35

私はPythonを学び、MutableDefaultArgumentの問題を扱っています。

# BAD: if `a_list` is not passed in, the default will wrongly retain its contents between successive function calls
def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

# GOOD: if `a_list` is not passed in, the default will always correctly be []
def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

a_listこれは、ステートメントが最初に検出されたときにのみ初期化されることを理解しています。そのdefため、以降の呼び出しでbad_append同じリストオブジェクトが使用されます。

私が理解していないのは、なぜgood_append動作が異なるのかということです。まだ一度だけ初期化されるa_listようです。したがって、このステートメントは関数の最初の呼び出しでのみtrueになります。つまり、最初の呼び出しでのみリセットされます。つまり、過去のすべての値が累積され、バグが発生します。ifa_list[]new_item

なんでじゃないの?どのような概念が欠けていますか?a_list実行するたびにどのようにきれいに拭き取られgood_appendますか?

4

5 に答える 5

30

a_listはまだ1回だけ初期化されるようです

Pythonの変数は単なる名前であるため、「初期化」はPythonの変数に発生することではありません。「初期化」はオブジェクトに対してのみ発生し、クラスの__init__メソッドを介して行われます。

あなたが書くときa = 0、それは割り当てです。つまり、「a表現によって記述されるオブジェクトを参照する」ということ0です。初期化ではありません。a後でいつでも他のタイプの名前を付けることができます。これは、に何か他のものを割り当てた結果として発生しaます。割り当ては単なる割り当てです。最初のものは特別ではありません。

あなたが書くときdef good_append(new_item, a_list=None)、それは「初期化」ではありませんa_list。これは、評価の結果であるオブジェクトへの内部参照を設定しているNoneためgood_append、2番目のパラメーターなしでが呼び出されると、そのオブジェクトは自動的にに割り当てられa_listます。

つまり、a_listは最初の呼び出しでのみ[]にリセットされます

いいえ、最初からいつでもa_list設定されます。つまり、いずれかが明示的に渡された場合、または引数が省略された場合です。[]a_listNoneNone

このコンテキストでは式が1回だけ評価されるため、の問題が[]発生します。関数がコンパイルされ、評価されると、特定のリストオブジェクトが作成され(開始時には空になります)、そのオブジェクトがデフォルトとして使用されます。 [][]

a_list実行するたびにどのようにきれいに拭き取られgood_appendますか?

そうではありません。する必要はありません。

問題が「可変のデフォルト引数」にあるとどのように説明されているか知っていますか?

None変更できません。

この問題は、パラメータがデフォルトとして持っているオブジェクトを変更するときに発生します。

a_list = []a_list以前に参照されたオブジェクトは変更されません。できない; 任意のオブジェクトは、その場で空のリストに魔法のように変換することはできません。a_list = []a_list以前に言及したものを参照するのをやめ、[]」を参照し始めることを意味します。以前に参照されたオブジェクトは変更されません。

関数がコンパイルされ、引数の1つにデフォルト値がある場合、その値(オブジェクト)が関数にベイクされます(これ自体もオブジェクトです!)。オブジェクトを変更するコードを作成すると、オブジェクトが変更されます。参照されているオブジェクトがたまたま関数にベイクされたオブジェクトである場合でも、それは変化します。

ただし、変更することはできませんNone。不変です。

変更できます[]。これはリストであり、リストは変更可能です。リストにアイテムを追加すると、リストが変更されます。

于 2012-05-20T20:59:44.047 に答える
19

のデフォルト値a_list(またはその他のデフォルト値)は、初期化されると関数の内部に保存されるため、次のように変更できます。

>>> def f(x=[]): return x
...
>>> f.func_defaults
([],)
>>> f.func_defaults[0] is f()
True

それぞれ Python 3の場合:

>>> def f(x=[]): return x
...
>>> f.__defaults__
([],)
>>> f.__defaults__[0] is f()
True

したがって、inの値func_defaultsは、関数内でよく知られているものと同じです(そして、私の例では、外部からアクセスするために返されます。

つまり、呼び出し時に何が起こるかf()は暗黙的x = f.func_defaults[0]です。そのオブジェクトが後で変更された場合、その変更を保持します。

対照的に、関数の割り当ては常に新しいを取得します[]。変更は、それへの最後の参照[]がなくなるまで続きます。次の関数呼び出しで、新しい[]ものが作成されます。

言い換えると、すべての実行で同じオブジェクトを取得することは真実ではありません[]が、(デフォルトの引数の場合)一度だけ実行されてから保存されます。

于 2012-05-20T20:42:12.840 に答える
17

この問題は、デフォルトが変更可能である場合にのみ存在しますが、変更可能でNoneはありません。関数オブジェクトと一緒に保存されるのはデフォルト値です。関数が呼び出されると、関数のコンテキストはデフォルト値で初期化されます。

a_list = []

a_list現在の関数呼び出しのコンテキストで、名前に新しいオブジェクトを割り当てるだけです。変更はありませんNone

于 2012-05-20T20:03:19.213 に答える
4

いいえ、ingood_insert a_listは一度だけ初期化されません。

引数を指定せずに関数が呼び出されるたびa_listに、デフォルトが使用され、の新しいインスタンスlistが使用されて返されますが、新しいリストはデフォルト値を置き換えません。

于 2012-05-20T20:36:07.817 に答える
0

Pythonチュートリアルはそれを言います

デフォルト値は1回だけ評価されます。

評価された(一度だけ)デフォルト値は内部に保存されます(x簡単にするために名前を付けます)。

ケース[]a_listデフォルト で関数を定義する場合、指定[]しないと、。のときに内部変数が割り当てられます。したがって、に追加すると、実際にはに追加されます(同じ変数を参照しているため)。なしで関数を再度呼び出すと、更新されたものがに再割り当てされます。a_listxa_listxa_listxa_listxa_list

ケースNoneNoneは1回評価され、に格納されxます。、を指定しない場合a_list、変数xはa_listに割り当てられます。しかし、もちろんあなたは追加しませんx。空の配列をに再割り当てしa_listます。この時点xで、a_listは異なる変数です。なしで関数を再度呼び出す場合と同じようにa_list、最初にから値Noneを取得しますxが、次にa_listが空の配列に再度割り当てられます。

このa_list = []場合、a_list関数を呼び出すときに明示的な値を指定すると、新しい引数はx1回だけ評価されるため、オーバーライドされないことに注意してください。

于 2020-05-11T20:29:40.737 に答える