3081

十分長い間 Python をいじっている人は誰でも、次の問題に悩まされています (またはバラバラに引き裂かれています)。

def foo(a=[]):
    a.append(5)
    return a

Python の初心者は、この関数が常に 1 つの要素のみを含むリストを返すことを期待するでしょう: [5]. その代わりに、結果は非常に異なっており、非常に驚​​くべきものです (初心者にとって):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

私のマネージャーはかつてこの機能に初めて遭遇し、言語の「劇的な設計上の欠陥」と呼んでいました。私は、この動作には根本的な説明があると答えました。内部構造を理解していないと、非常に不可解で予想外です。ただし、次の質問に (自分自身で) 答えることができませんでした: 関数の実行時ではなく、関数の定義時に既定の引数をバインドする理由は何ですか? 経験豊富な動作が実際に使用できるとは思えません (バグを繁殖させることなく、C で静的変数を実際に使用したのは誰ですか?)

編集

Baczek は興味深い例を示しました。あなたのコメントのほとんどと特にウタールのコメントとともに、私はさらに詳しく説明しました。

>>> def a():
...     print("a executed")
...     return []
... 
>>>            
>>> def b(x=a()):
...     x.append(5)
...     print(x)
... 
a executed
>>> b()
[5]
>>> b()
[5, 5]

私には、設計上の決定は、パラメーターのスコープをどこに配置するかに関連しているように見えます: 関数内、またはそれと「一緒に」?

関数内でバインドを行うxと、関数が呼び出されたときに指定されたデフォルトに効果的にバインドされ、定義されていないことを意味します。これは、深刻な欠陥をもたらすものです:def行は、バインドの一部 (の関数オブジェクト) は定義時に発生し、一部 (デフォルト パラメーターの割り当て) は関数呼び出し時に発生します。

実際の動作はより一貫しています。その行が実行されるとき、つまり関数定義時に、その行のすべてが評価されます。

4

31 に答える 31

1806

実際、これは設計上の欠陥ではなく、内部やパフォーマンスが原因ではありません。
これは単純に、Python の関数が第一級のオブジェクトであり、単なるコードではないという事実から来ています。

このように考えるとすぐに、それは完全に理にかなっています。関数は、その定義で評価されるオブジェクトです。デフォルトのパラメーターは一種の「メンバー データ」であるため、他のオブジェクトとまったく同じように、呼び出しごとに状態が変化する可能性があります。

いずれにせよ、Effbot はDefault Parameter Values in Pythonでこの動作の理由について非常に優れた説明をしています。
非常に明確で、関数オブジェクトがどのように機能するかをよりよく理解するために読むことを強くお勧めします。

于 2009-07-17T21:29:39.827 に答える
306

次のコードがあるとします

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

eat の宣言を見ると、最初のパラメーターが指定されていない場合、それがタプルと等しいと考えるのが最も驚くべきことではありません。("apples", "bananas", "loganberries")

ただし、コードの後半で、次のようなことをするとします。

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

次に、関数の宣言ではなく関数の実行時にデフォルトのパラメーターがバインドされていた場合、果物が変更されたことを発見して(非常に悪い方法で)驚かれることでしょう。fooこれは、上記の関数がリストを変更していることを発見するよりも驚くべき IMO です。

本当の問題は変更可能な変数にあり、すべての言語にはこの問題がある程度あります。ここで質問です。Java で次のコードがあるとします。

StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) );  // does this work?

さて、マップはStringBufferマップに配置されたときにキーの値を使用しますか、それとも参照によってキーを保存しますか? いずれにせよ、誰かが驚いています。入れたものと同じ値を使用してオブジェクトを取り出そうMapとした人、または使用しているキーが文字通り同じオブジェクトであるにもかかわらず、オブジェクトを取得できないように見える人。これは、それをマップに入れるために使用されました (これが実際に、Python が変更可能な組み込みデータ型を辞書のキーとして使用することを許可していない理由です)。

あなたの例は、Pythonの初心者が驚いて噛まれる良い例です。しかし、これを「修正」した場合、代わりに噛まれるという別の状況が発生するだけであり、さらに直感的でなくなると私は主張します. さらに、これは可変変数を扱う場合には常に当てはまります。書いているコードに応じて、誰かが直観的にどちらかの動作または反対の動作を期待できる場合に常に遭遇します。

個人的には、Python の現在のアプローチが気に入っています。デフォルトの関数引数は、関数が定義されたときに評価され、そのオブジェクトが常にデフォルトになります。空のリストを使用して特別なケースを作成できると思いますが、そのような特別なケースを使用すると、下位互換性がないことは言うまでもなく、さらに驚くでしょう。

于 2009-07-15T18:11:26.213 に答える
279

ドキュメントの関連部分:

関数定義が実行されると、デフォルトのパラメーター値が左から右に評価されます。これは、関数が定義されるときに式が一度評価され、同じ「事前に計算された」値が各呼び出しに使用されることを意味します。これは、既定のパラメーターがリストや辞書などの変更可能なオブジェクトである場合に特に重要です。関数がオブジェクトを変更する場合 (たとえば、項目をリストに追加することによって)、既定値は事実上変更されます。これは通常、意図したものではありません。これを回避する方法はNone、デフォルトとして使用し、関数の本体で明示的にテストすることです。次に例を示します。

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin
于 2012-07-10T14:50:42.877 に答える
131

私は Python インタープリターの内部動作については何も知りません (また、コンパイラーやインタープリターの専門家でもありません)。

Python オブジェクトが変更可能であれば、デフォルトの引数を設計するときにこれを考慮に入れる必要があると思います。リストをインスタンス化する場合:

a = []

によって参照される新しいリストを取得することを期待していますa

なぜa=[]

def x(a=[]):

呼び出しではなく関数定義で新しいリストをインスタンス化しますか? 「ユーザーが引数を提供しない場合は、新しいリストをインスタンス化し、呼び出し元によって作成されたかのように使用する」ように求めているのと同じです。代わりに、これはあいまいだと思います:

def x(a=datetime.datetime.now()):

ユーザー、a定義または実行時に対応する日時をデフォルトにしますxか? この場合、前のものと同様に、デフォルトの引数「代入」が関数の最初の命令 (datetime.now()関数呼び出しで呼び出される) であるかのように、同じ動作を維持します。一方、定義時のマッピングが必要な場合は、次のように記述できます。

b = datetime.datetime.now()
def x(a=b):

私は知っています、私は知っています:それは閉鎖です。別の方法として、Python は定義時のバインディングを強制するキーワードを提供する場合があります。

def x(static a=b):
于 2009-07-15T23:21:09.207 に答える
89

その理由は非常に単純で、コードが実行されるときにバインディングが行われ、関数が定義されるときに関数定義が実行されるからです。

これを比較してください:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

このコードは、まったく同じ予期しない出来事に悩まされています。バナナはクラス属性であるため、何かを追加すると、そのクラスのすべてのインスタンスに追加されます。理由は全く同じです。

それは単なる「仕組み」であり、関数の場合に異なる動作をさせることはおそらく複雑であり、クラスの場合には不可能である可能性が高く、または少なくともオブジェクトのインスタンス化を大幅に遅くします。クラスコードを維持する必要があるからです。オブジェクトの作成時に実行します。

はい、予想外です。しかし、ペニーが落ちると、Python の一般的な動作に完全に適合します。実際、これは優れた教材であり、なぜこれが起こるのかを理解すれば、Python をよりよく理解できるようになります。

つまり、優れた Python チュートリアルで目立つように機能する必要があります。あなたが言及したように、誰もが遅かれ早かれこの問題に遭遇するからです。

于 2009-07-15T18:54:45.767 に答える
78

内省しませんか?

Callable に対して Python (およびapply) が提供する洞察に満ちたイントロスペクションを誰も実行していないことに、私は本当に驚いています。23

func次のように定義された単純な小さな関数があるとします。

>>> def func(a = []):
...    a.append(5)

Python がこれに遭遇すると、最初に行うことはcode、この関数のオブジェクトを作成するためにコンパイルすることです。このコンパイル手順が完了すると、Pythonは* を評価し、デフォルトの引数 (ここでは空のリスト) を関数オブジェクト自体に格納[]します。一番上の回答が述べたように、リストは functionのメンバーaと見なすことができます。func

それでは、リストが関数オブジェクトでどのように展開されるかを調べるために、前後の内省を行いましょう。私はPython 3.xこれに使用しています.Python 2にも同じことが当てはまります( Python 2では__defaults__orfunc_defaultsを使用します;はい、同じものに2つの名前があります)。

実行前の関数:

>>> def func(a = []):
...     a.append(5)
...     

Python がこの定義を実行した後、指定されたデフォルト パラメータ (a = []ここ)を受け取り、それらを関数オブジェクトの属性に詰め込み__defaults__ます(関連セクション: Callables):

>>> func.__defaults__
([],)

わかりました。予想どおり、空のリストが の単一のエントリとして表示__defaults__されます。

実行後の機能:

この関数を実行してみましょう:

>>> func()

__defaults__それでは、それらをもう一度見てみましょう。

>>> func.__defaults__
([5],)

驚いた?オブジェクト内の値が変化します! 関数を連続して呼び出すと、その埋め込みlistオブジェクトに単純に追加されます。

>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)

この「欠陥」が発生する理由は、デフォルト引数が関数オブジェクトの一部であるためです。ここで奇妙なことは何も起こっていません。すべてが少し驚くべきことです。

これに対処するための一般的な解決策はNone、デフォルトとして使用し、関数本体で初期化することです。

def func(a = None):
    # or: a = [] if a is None else a
    if a is None:
        a = []

関数本体は毎回新たに実行されるため、 に引数が渡されなかった場合、常に新しい空のリストが取得されaます。


__defaults__のリストが関数で使用されているものと同じであることをさらに確認するには、関数本体内で使用されるリストのfuncを返すように関数を変更するだけです。次に、( の位置) のリストと比較すると、これらが実際に同じリスト インスタンスをどのように参照しているかがわかります。ida__defaults__[0]__defaults__

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True

すべては内省の力で!


*関数のコンパイル中に Python がデフォルトの引数を評価することを確認するには、次のコマンドを実行してみてください。

def bar(a=input('Did you just see me without calling the function?')): 
    pass  # use raw_input in Py2

お気づきのようinput()に、関数を構築して名前にバインドするプロセスが行われる前に呼び出さbarれます。

于 2015-12-09T07:13:28.063 に答える
64

以前は、実行時にオブジェクトを作成する方が良いアプローチだと思っていました。初心者の混乱を防ぐためだけにそれだけの価値があるかもしれませんが、あなたはいくつかの便利な機能を失うので、私は今は確信が持てません。そうすることの欠点は次のとおりです。

1.パフォーマンス

def foo(arg=something_expensive_to_compute())):
    ...

呼び出し時の評価が使用される場合、関数が引数なしで使用されるたびに、高価な関数が呼び出されます。呼び出しごとに高額な料金を支払うか、値を手動で外部にキャッシュして、名前空間を汚染し、冗長性を追加する必要があります。

2.バインドされたパラメータを強制する

便利なトリックは、ラムダの作成時にラムダのパラメーターを変数の現在のバインディングにバインドすることです。例えば:

funcs = [ lambda i=i: i for i in range(10)]

これは、それぞれ0、1、2、3...を返す関数のリストを返します。動作が変更された場合、代わりにiの呼び出し時iの値にバインドされるため、すべてが返される関数のリストを取得します。9

それ以外の方法でこれを実装する唯一の方法は、iバウンドでさらにクロージャーを作成することです。

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3.内省

コードを考えてみましょう:

def foo(a='test', b=100, c=[]):
   print a,b,c

inspectモジュールを使用して、引数とデフォルトに関する情報を取得できます。

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

この情報は、ドキュメントの生成、メタプログラミング、デコレータなどに非常に役立ちます。

ここで、デフォルトの動作を変更して、これが次と同等になると仮定します。

_undefined = object()  # sentinel value

def foo(a=_undefined, b=_undefined, c=_undefined)
    if a is _undefined: a='test'
    if b is _undefined: b=100
    if c is _undefined: c=[]

ただし、イントロスペクトして、デフォルトの引数何であるかを確認する機能が失われました。オブジェクトは作成されていないため、実際に関数を呼び出さずにオブジェクトを取得することはできません。私たちができる最善のことは、ソースコードを保存し、それを文字列として返すことです。

于 2009-07-16T10:05:09.600 に答える
56

この動作は、次のように簡単に説明できます。

  1. 関数(クラスなど)宣言は一度だけ実行され、すべてのデフォルト値オブジェクトが作成されます
  2. すべてが参照によって渡されます

そう:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a変わらない - すべての代入呼び出しが新しい int オブジェクトを作成する - 新しいオブジェクトが出力される
  2. b変更されません - 新しい配列はデフォルト値から構築され、出力されます
  3. c変更 - 操作は同じオブジェクトに対して実行され、出力されます
于 2009-07-15T19:15:25.553 に答える
37

1) いわゆる「可変デフォルト引数」の問題は、一般に次のことを示す特別な例です:
「この問題を伴うすべての関数は、実際のパラメーターに対する同様の副作用の問題も抱えています。」
これは、関数型プログラミングの規則に反しています。通常は望ましくないため、両方を一緒に修正する必要があります。

例:

def foo(a=[]):                 # the same problematic function
    a.append(5)
    return a

>>> somevar = [1, 2]           # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5]                      # usually expected [1, 2]

解決策:コピー
絶対に安全な解決策は、最初にcopyまたはdeepcopy入力オブジェクトを作成してから、そのコピーを使用して何かを行うことです。

def foo(a=[]):
    a = a[:]     # a copy
    a.append(5)
    return a     # or everything safe by one line: "return a + [5]"

多くの組み込み可変型には、 or のようなコピー メソッドがあり、orsome_dict.copy()some_set.copy()ように簡単にコピーできます。すべてのオブジェクトは、またはより完全にコピーすることもできます(後者は、変更可能なオブジェクトが変更可能なオブジェクトから構成されている場合に役立ちます)。一部のオブジェクトは、基本的に「ファイル」オブジェクトのような副作用に基づいており、コピーによって意味のある再現ができません。コピーするsomelist[:]list(some_list)copy.copy(any_object)copy.deepcopy()

同様の SO 質問の問題例

class Test(object):            # the original problematic class
  def __init__(self, var1=[]):
    self._var1 = var1

somevar = [1, 2]               # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar                  # [1, 2, [1]] but usually expected [1, 2]
print t2._var1                 # [1, 2, [1]] but usually expected [1, 2]

この関数によって返されるインスタンスのパブリック属性にも保存しないでください。(インスタンスのプライベート属性は、慣例により、このクラスまたはサブクラスの外部から変更されるべきではないと仮定します。つまり_var1、プライベート属性です)

結論:
入力パラメーター オブジェクトは、その場で変更 (変異) したり、関数によって返されるオブジェクトにバインドしたりしないでください。(副作用のないプログラミングを好む場合は、これを強くお勧めします。Wikiの「副作用」を参照してください(このコンテキストでは、最初の 2 つの段落が関連しています。)

2)
実際のパラメーターへの副作用が必要であるが、デフォルトのパラメーターでは望ましくない場合にのみ、有用な解決策はdef ...(var1=None): if var1 is None: var1 = [] More..

3) 場合によっては、デフォルト パラメータの変更可能な動作が便利です

于 2012-11-22T18:09:04.030 に答える
37

あなたが求めているのは、これがなぜなのかです:

def func(a=[], b = 2):
    pass

これと内部的に同等ではありません:

def func(a=None, b = None):
    a_default = lambda: []
    b_default = lambda: 2
    def actual_func(a=None, b=None):
        if a is None: a = a_default()
        if b is None: b = b_default()
    return actual_func
func = func()

func(None, None) を明示的に呼び出す場合を除いて、無視します。

つまり、デフォルトのパラメーターを評価する代わりに、それぞれを保存して、関数が呼び出されたときに評価してみませんか?

1 つの答えはおそらくそこにあります。それは、デフォルトのパラメーターを持つすべての関数を効果的にクロージャーに変えることです。すべてがインタープリターに隠されていても、完全なクロージャーではなくても、データはどこかに保存する必要があります。遅くなり、より多くのメモリを使用します。

于 2009-07-15T20:18:14.973 に答える
30

これは実際にはデフォルト値とは何の関係もありませんが、変更可能なデフォルト値を持つ関数を作成するときに予期しない動作として頻繁に発生することを除けば.

>>> def foo(a):
    a.append(5)
    print a

>>> a  = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]

このコードにはデフォルト値はありませんが、まったく同じ問題が発生します。

問題は、呼び出し元がこれを予期していないときに、呼び出し元から渡された可変変数を変更fooしていることです。関数が次のように呼び出された場合、このようなコードは問題ありません。その場合、呼び出し元は、渡された値を変更するために関数を呼び出し、動作が期待されます。しかし、そのような関数がデフォルトの引数を取る可能性は非常に低く、おそらくリストを返さないでしょう (呼び出し元がそのリストへの参照を既に持っているためです。つまり、渡されたリストです)。append_5

fooデフォルトの引数を持つ元の は、a明示的に渡されたのか、デフォルト値を取得したのかを変更するべきではありません。引数が変更されるはずであることがコンテキスト/名前/ドキュメントから明らかでない限り、コードは変更可能な引数をそのままにしておく必要があります。引数として渡された変更可能な値をローカル一時変数として使用することは、Python を使用しているかどうか、およびデフォルトの引数が含まれているかどうかに関係なく、非常に悪い考えです。

何かを計算する過程でローカル テンポラリを破壊的に操作する必要があり、引数値から操作を開始する必要がある場合は、コピーを作成する必要があります。

于 2011-05-23T04:24:30.597 に答える
27

パフォーマンスの最適化です。この機能の結果として、これら 2 つの関数呼び出しのどちらが速いと思いますか?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2

ヒントをあげます。逆アセンブリは次のとおりです(http://docs.python.org/library/dis.htmlを参照):

#1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

#2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

経験豊富な動作が実際に使用できるとは思えません (バグを繁殖させることなく、C で静的変数を実際に使用したのは誰ですか?)

ご覧のとおり、不変の既定の引数を使用すると、パフォーマンスが向上します。これは、頻繁に呼び出される関数であるか、デフォルトの引数の構築に時間がかかる場合に違いを生む可能性があります。また、Python は C ではないことに注意してください。C には、ほとんど自由な定数があります。Python では、この利点はありません。

于 2009-07-15T23:18:36.577 に答える
26

Python: 変更可能な既定の引数

デフォルトの引数は、関数が関数オブジェクトにコンパイルされるときに評価されます。関数によって複数回使用された場合、それらは同じオブジェクトのままです。

それらが変更可能である場合、変更された場合 (たとえば、要素を追加することによって)、連続した呼び出しで変更されたままになります。

それらは毎回同じオブジェクトであるため、変異したままです。

同等のコード:

関数オブジェクトがコンパイルされてインスタンス化されると、リストは関数にバインドされるため、次のようになります。

def foo(mutable_default_argument=[]): # make a list the default argument
    """function that uses a list"""

これとほぼ同じです:

_a_list = [] # create a list in the globals

def foo(mutable_default_argument=_a_list): # make it the default argument
    """function that uses a list"""

del _a_list # remove globals name binding

デモンストレーション

ここにデモンストレーションがあります - によって参照されるたびに、それらが同じオブジェクトであることを確認できます

  • 関数が関数オブジェクトへのコンパイルを完了する前にリストが作成されることを確認し、
  • リストが参照されるたびにIDが同じであることを観察し、
  • それを使用する関数が2回目に呼び出されたときにリストが変更されたままになることを観察し、
  • 出力がソースから出力される順序を観察します (便宜上、番号を付けました)。

example.py

print('1. Global scope being evaluated')

def create_list():
    '''noisily create a list for usage as a kwarg'''
    l = []
    print('3. list being created and returned, id: ' + str(id(l)))
    return l

print('2. example_function about to be compiled to an object')

def example_function(default_kwarg1=create_list()):
    print('appending "a" in default default_kwarg1')
    default_kwarg1.append("a")
    print('list with id: ' + str(id(default_kwarg1)) + 
          ' - is now: ' + repr(default_kwarg1))

print('4. example_function compiled: ' + repr(example_function))


if __name__ == '__main__':
    print('5. calling example_function twice!:')
    example_function()
    example_function()

そしてそれを実行しpython example.pyます:

1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']

これは「最小の驚き」の原則に違反していますか?

この実行順序は、Python の新しいユーザーを混乱させることがよくあります。Python 実行モデルを理解していれば、それは当然のことです。

新しい Python ユーザーへの通常の指示:

しかし、これが、新しいユーザーへの通常の指示が、代わりに次のようにデフォルト引数を作成することである理由です。

def example_function_2(default_kwarg=None):
    if default_kwarg is None:
        default_kwarg = []

これは、None シングルトンをセンチネル オブジェクトとして使用して、デフォルト以外の引数を取得したかどうかを関数に伝えます。引数を取得しない場合、実際には新しい空のリスト を[]デフォルトとして使用したいと考えています。

制御フローに関するチュートリアルのセクションにあるように:

後続の呼び出し間でデフォルトを共有したくない場合は、代わりに次のように関数を記述できます。

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
于 2016-05-01T16:20:44.383 に答える
25

最短の答えはおそらく「定義は実行である」であるため、議論全体は厳密には意味がありません。より不自然な例として、これを引用できます。

def a(): return []

def b(x=a()):
    print x

ステートメントの実行時にデフォルトの引数式を実行defしないことは簡単ではないか、意味がないか、あるいはその両方であることを示すだけで十分だと思います。

ただし、デフォルトのコンストラクターを使用しようとすると、問題があることに同意します。

于 2009-07-16T12:19:23.603 に答える
23

次の点を考慮すれば、この動作は驚くべきことではありません。

  1. 割り当て試行時の読み取り専用クラス属性の動作、およびその
  2. 関数はオブジェクトです(受け入れられた回答でよく説明されています)。

(2)の役割は、このスレッドで広く取り上げられています。(1)他の言語から来ている場合、この動作は「直感的」ではないため、驚きを引き起こす要因である可能性があります。

(1)は Pythonチュートリアル on classesで説明されています。読み取り専用クラス属性に値を割り当てようとしている場合:

...最も内側のスコープの外にあるすべての変数は読み取り専用です (そのような変数に書き込もうとすると、最も内側のスコープに新しいローカル変数が作成され、同じ名前の外部変数は変更されません)。

元の例に戻って、上記の点を検討してください。

def foo(a=[]):
    a.append(5)
    return a

これfooはオブジェクトでaあり、の属性ですfoo( で入手可能foo.func_defs[0])。aはリストであるためa変更可能であり、したがって の読み取り/書き込み属性ですfoo。関数がインスタンス化されると、署名によって指定された空のリストに初期化され、関数オブジェクトが存在する限り、読み取りと書き込みに使用できます。

デフォルトをオーバーライドせずに呼び出すfooと、 からのデフォルトの値が使用されますfoo.func_defs。この場合、関数オブジェクトのコード スコープ内foo.func_defs[0]で使用されます。オブジェクトの一部であり、 のコードの実行間で持続するchangeaへの変更。afoo.func_defs[0]foofoo

ここで、関数が実行されるたびに関数シグネチャのデフォルトが使用されるように、これを他の言語のデフォルト引数動作のエミュレートに関するドキュメントの例と比較します。

def foo(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

(1)(2)を考慮に入れると、これが望ましい動作を達成する理由がわかります。

  • foo関数オブジェクトがインスタンス化されると、不変オブジェクトである に設定foo.func_defs[0]されます。None
  • 関数がデフォルトで実行される場合 (L関数呼び出しでパラメーターが指定されていない場合)、foo.func_defs[0]( None) はローカル スコープで として使用できますL
  • では、その属性が読み取り専用であるためL = []、割り当ては成功しません。foo.func_defs[0]
  • (1)に従って、名前も付けられた新しいローカル変数Lがローカル スコープで作成され、残りの関数呼び出しに使用されます。foo.func_defs[0]したがって、今後の の呼び出しでは変更されませんfoo
于 2012-04-24T19:43:13.090 に答える
21

Noneを使用した簡単な回避策

>>> def bar(b, data=None):
...     data = data or []
...     data.append(b)
...     return data
... 
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]
于 2013-02-28T11:10:16.957 に答える
19

デフォルトのリスト値を関数に渡す代替構造を示します (辞書でも同様に機能します)。

他の人が広くコメントしているように、リストパラメーターは、実行時ではなく定義時に関数にバインドされます。リストと辞書は可変であるため、このパラメーターを変更すると、この関数の他の呼び出しに影響します。その結果、関数への後続の呼び出しは、関数への他の呼び出しによって変更された可能性があるこの共有リストを受け取ります。さらに悪いことに、2 つのパラメーターがこの関数の共有パラメーターを同時に使用していて、他のパラメーターが行った変更に気付かないことがあります。

間違った方法 (おそらく...) :

def foo(list_arg=[5]):
    return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]  

# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()             
7

以下を使用して、それらが同一のオブジェクトであることを確認できますid

>>> id(a)
5347866528

>>> id(b)
5347866528

Brett Slatkin の「Effective Python: 59 Specific Ways to Write Better Python」、項目 20:Noneおよび Docstrings を使用して動的なデフォルト引数を指定する(p. 48)

Python で目的の結果を達成するための規則は、デフォルト値を提供Noneし、docstring で実際の動作を文書化することです。

この実装により、関数への各呼び出しが既定のリストを受け取るか、関数に渡されたリストを受け取ることが保証されます。

推奨方法:

def foo(list_arg=None):
   """
   :param list_arg:  A list of input values. 
                     If none provided, used a list with a default value of 5.
   """
   if not list_arg:
       list_arg = [5]
   return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
>>> b
[5, 7]

c = foo([10])
c.append(11)
>>> c
[10, 11]

プログラマーがデフォルトのリストパラメーターを共有することを意図した「間違った方法」の正当な使用例があるかもしれませんが、これは規則よりも例外である可能性が高くなります。

于 2015-09-12T06:00:51.537 に答える
17

ここでの解決策は次のとおりです。

  1. Noneデフォルト値 (または nonce ) として使用しobject、それをオンにして実行時に値を作成します。また
  2. デフォルトのパラメーターとしてa を使用し、lambdaそれを try ブロック内で呼び出してデフォルト値を取得します (これは、ラムダ抽象化が目的とするものです)。

2 番目のオプションは、関数のユーザーが既に存在している可能性のある呼び出し可能オブジェクト ( などtype)を渡すことができるため、便利です。

于 2012-03-20T17:22:11.860 に答える
16

それは本当かもしれません:

  1. 誰かがすべての言語/ライブラリ機能を使用していて、
  2. ここで動作を切り替えることはお勧めできませんが、

上記の両方の機能を保持し、さらに別の点を指摘することは完全に一貫しています。

  1. これは紛らわしい機能であり、Pythonでは残念です。

他の答え、または少なくともそれらのいくつかは、ポイント1と2を作成しますが、3は作成しません。または、ポイント3を作成し、ポイント1と2を軽視します。しかし、3つすべてが当てはまります。

ここで途中で馬を切り替えると、重大な破損が発生する可能性があります。また、Pythonを変更してStefanoのオープニングスニペットを直感的に処理することで、さらに多くの問題が発生する可能性があります。そして、Pythonの内部をよく知っている人が、結果の地雷原を説明できるのは事実かもしれません。でも、

既存の動作はPythonicではなく、Pythonは成功します。これは、言語についてほとんど、驚き最小の原則に違反していないためです。これはひどい。それを根こそぎにするのが賢明であるかどうかにかかわらず、それは本当の問題です。設計上の欠陥です。動作を追跡することで言語をよりよく理解できれば、C++はこれ以上のことをすべて実行していると言えます。たとえば、微妙なポインタエラーをナビゲートすることで多くのことを学びます。しかし、これはPythonicではありません。Pythonは他の言語よりも驚きがはるかに少ないため、この動作に直面しても耐えられるほどPythonに関心を持っている人は、その言語に惹かれる人です。Dabblersと好奇心旺盛な人は、Pythonに惹かれるプログラマーの直感に反する、設計上の問題(つまり、隠されたロジックパズル)が原因ではなく、何かが機能するのにかかる時間がどれだけ短いかに驚いたときにPythonistasになります。それはちょうどうまくいくからです。

于 2009-07-16T19:17:59.560 に答える
16

次のパターンの代わりに、この動作を利用することがあります。

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

singletonのみで使用される場合use_singleton、代わりに次のパターンが気に入っています。

# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
    return singleton.use_me()

私はこれを、外部リソースにアクセスするクライアント クラスのインスタンス化と、メモ化のための辞書またはリストの作成に使用しました。

このパターンはあまり知られていないと思うので、今後の誤解を防ぐために短いコメントを入れておきます。

于 2015-02-05T21:44:51.570 に答える
16

オブジェクトを置き換えることでこれを回避できます(したがって、スコープとのタイ):

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a

醜いですが、うまくいきます。

于 2013-01-15T11:02:03.340 に答える
16

これを行うと:

def foo(a=[]):
    ...

...呼び出し元が a の値を渡さない場合、名前のないリストにa引数を割り当てます。

この議論を簡単にするために、名前のないリストに一時的に名前を付けましょう。どうpavloですか?

def foo(a=pavlo):
   ...

いつでも、呼び出し元が何であるかを教えてくれない場合aは、 を再利用しpavloます。

pavloがミュータブル (変更可能) であり、最終的にそれを変更する場合foo、次に気付く効果はfooを指定せずに呼び出されaます。

したがって、これが表示されます(pavlo[]に初期化されていることに注意してください):

 >>> foo()
 [5]

さて、pavlo【5】です。

再度呼び出すfoo()と、再び変更pavloされます。

>>> foo()
[5, 5]

aいつ呼び出すかを指定すると、触れられないことがfoo()保証されます。pavlo

>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]

だから、pavloまだ[5, 5]です。

>>> foo()
[5, 5, 5]
于 2014-09-11T22:05:43.013 に答える
9

この「バグ」のせいで残業が多くなりました!しかし、私はそれの潜在的な用途を見始めています(しかし、私はそれが実行時にあればよかったのですが、それでも)

役立つ例として私が見たものを紹介します。

def example(errors=[]):
    # statements
    # Something went wrong
    mistake = True
    if mistake:
        tryToFixIt(errors)
        # Didn't work.. let's try again
        tryToFixItAnotherway(errors)
        # This time it worked
    return errors

def tryToFixIt(err):
    err.append('Attempt to fix it')

def tryToFixItAnotherway(err):
    err.append('Attempt to fix it by another way')

def main():
    for item in range(2):
        errors = example()
    print '\n'.join(errors)

main()

以下を印刷します

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way
于 2013-07-22T07:35:28.483 に答える
7

TLDR: 定義時のデフォルトは一貫しており、より表現力豊かです。


関数の定義は、関数を含む定義スコープと、関数に含まれる実行スコープの 2 つのスコープに影響します。ブロックがスコープにどのようにマップされるかは明らかですが、問題はどこにdef <name>(<args=defaults>):属しているかです。

...                           # defining scope
def name(parameter=default):  # ???
    ...                       # execution scope

def nameパーツは定義されたスコープで評価する必要があります。結局のところ、そこで利用できるようにしたいのですname。関数を内部でのみ評価すると、アクセスできなくなります。

は定数名であるためparameter、 と同時に「評価」できdef nameます。これには、既知のシグネチャを持つ関数を生のname(parameter=...):ではなくとして生成するという利点もありname(...):ます。

さて、いつ評価するのdefaultですか?

一貫性はすでに「定義時」と言っています。他のすべてのものもdef <name>(<args=defaults>):定義時に最もよく評価されます。その一部を遅らせることは、驚くべき選択です。

2 つの選択肢も同等ではありません。defaultが定義時に評価される場合でも、実行時間に影響を与える可能性があります。defaultが実行時に評価される場合、定義時間には影響しません。「定義時」を選択すると、両方のケースを表現できますが、「実行時」を選択すると、1 つのみを表現できます。

def name(parameter=defined):  # set default at definition time
    ...

def name(parameter=default):     # delay default until execution time
    parameter = default if parameter is None else parameter
    ...
于 2018-12-15T12:09:01.010 に答える
6

この質問に対する答えは、可変性やpythonが「def」ステートメントを処理する方法ではなく、pythonがデータをパラメーターに渡す方法(値渡しまたは参照渡し)にあると思います。

簡単な紹介。まず、Python には 2 種類のデータ型があり、1 つは数値のような単純な基本データ型で、もう 1 つはオブジェクトです。次に、データをパラメータに渡すとき、Python は基本データ型を値で渡します。つまり、値のローカル コピーをローカル変数に作成しますが、オブジェクトは参照で渡します。つまり、オブジェクトへのポインタです。

上記の 2 点を認めて、Python コードに何が起こったのかを説明しましょう。これは、オブジェクトの参照渡しのためだけですが、変更可能/不変、またはおそらく「def」ステートメントが定義されたときに一度だけ実行されるという事実とは関係ありません。

[] はオブジェクトなので、python は [] の参照を に渡しますa。つまり、aオブジェクトとしてメモリにある [] へのポインタにすぎません。[] のコピーは 1 つしかありませんが、多くの参照があります。最初の foo() では、リスト [] がappend メソッドによって1に変更されます。ただし、リスト オブジェクトのコピーは 1 つだけであり、このオブジェクトは1になることに注意してください。2 番目の foo() を実行すると、effbot Web ページの内容 (items は評価されなくなります) が間違っています。aはリスト オブジェクトとして評価されますが、オブジェクトの内容は1になります。これが参照渡しの効果です!foo(3) の結果は、同じ方法で簡単に導出できます。

私の答えをさらに検証するために、2 つの追加コードを見てみましょう。

====== No.2 ========

def foo(x, items=None):
    if items is None:
        items = []
    items.append(x)
    return items

foo(1)  #return [1]
foo(2)  #return [2]
foo(3)  #return [3]

[]はオブジェクトですNone(前者は変更可能ですが、後者は不変です。ただし、変更可能性は問題とは関係ありません)。None は空間のどこかにありますが、そこにあることはわかっており、そこには None のコピーが 1 つしかありません。したがって、foo が呼び出されるたびに、items は (1 回だけ評価されるという回答とは対照的に) None と評価されます。明確に言うと、None の参照 (またはアドレス) です。次に、foo で item が [] に変更されます。つまり、別のアドレスを持つ別のオブジェクトを指します。

====== No.3 =======

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)    # returns [1]
foo(2,[]) # returns [2]
foo(3)    # returns [1,3]

foo(1) の呼び出しにより、項目は、たとえば 11111111 などのアドレスを持つリスト オブジェクト [] を指します。リストの内容は、後続の foo 関数で1に変更されますが、アドレスは変更されず、11111111 のままです。 . そして foo(2,[]) が来る。foo(2,[]) の [] は、foo(1) を呼び出したときのデフォルト パラメータ [] と同じ内容ですが、それらのアドレスは異なります。パラメータを明示的に提供するため、itemsこの new のアドレス[]、たとえば 2222222 を取得し、何らかの変更を加えた後にそれを返す必要があります。これで foo(3) が実行されます。からのみxが提供されると、アイテムは再びデフォルト値を取る必要があります。デフォルト値は?これは foo 関数を定義するときに設定されます: 11111111 にあるリスト オブジェクトです。したがって、項目は、要素 1 を持つアドレス 11111111 であると評価されます。2222222 にあるリストにも 1 つの要素 2 が含まれていますが、items any によってポイントされていません。もっと。したがって、3 を追加するとitems[1,3] になります。

上記の説明から、受け入れられた回答で推奨されているeffbot Web ページが、この質問に関連する回答を提供できなかったことがわかります。さらに、effbot Web ページのポイントが間違っていると思います。UI.Button に関するコードは正しいと思います:

for i in range(10):
    def callback():
        print "clicked button", i
    UI.Button("button %s" % i, callback)

各ボタンは、 の異なる値を表示する個別のコールバック関数を保持できますi。これを示す例を提供できます。

x=[]
for i in range(10):
    def callback():
        print(i)
    x.append(callback) 

実行するx[7]()と、予想どおり 7x[9]()が得られ、別の値である 9 が得られますi

于 2013-08-22T05:58:41.750 に答える