web-dev-qa-db-ja.com

「最小の驚き」と可変デフォルト引数

次のような問題で、Pythonをいじっていた人は誰でも噛み付いていました。

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

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

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

私のマネージャーはかつてこの機能に初めて出会い、それをこの言語の「劇的なデザインの欠陥」と呼びました。その振る舞いには根本的な説明があると私は答えました、そしてあなたが内部を理解しなければそれは確かに非常に不可解で予想外です。しかし、私は次の質問に答えることができませんでした。関数の実行時ではなく、関数の定義時にデフォルト引数をバインドする理由は何ですか。私が経験した振る舞いが実用的な用途を持っているのではないかと思います(バグを繁殖させずに、Cで静的変数を実際に使ったのは誰ですか?)

編集

Baczekは興味深い例を作りました。あなたのコメントのほとんど、特にUtaalのコメントと一緒に、私はさらに詳しく述べました:

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

私にとっては、設計上の決定は、パラメータの範囲をどこに置くかに関連しているように思われます。

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

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

2310
Stefano Borini

実際、これは設計上の欠陥ではなく、内部構造やパフォーマンスによるものではありません。
それは単にPythonの関数が一片のコードではなく、一流のオブジェクトであるという事実から来る。

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

いずれにせよ、Effbotはこの動作の理由について Pythonのデフォルトパラメータ値 にとてもよく説明しています。
私はそれが非常にはっきりしていると感じました、そして機能オブジェクトがどのように働くかのより良い知識のためにそれを読むことを本当に勧めます。

1486
rob

次のようなコードがあるとします。

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

def eat(food=fruits):
    ...

Eatの宣言を見ると、最も驚くべきことは、最初のパラメータが与えられていなければ、それはTupleの("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の初心者が驚いて噛まれるようなケースの良い例です。しかし、私たちがこれを「修正」した場合、それは代わりに噛み付かれることになるという別の状況を生み出すだけであり、直感的にはあまり理解できないと私は主張します。さらに、これは可変変数を扱うときはいつもそうです。自分が書いているコードに応じて、誰かが直感的に1つの動作またはその逆の動作を予期する可能性がある場合があります。

私は個人的にはPythonの現在のアプローチが好きです。デフォルトの関数引数は関数が定義されたときに評価され、そのオブジェクトは常にデフォルトです。私は彼らが空のリストを使用して特別な場合をすることができると思います、しかしその種の特別なケーシングはさらに不適合を引き起こすでしょう、後方互換性がないことは言うまでもありません。

255
Eli Courtwright

AFAICS ドキュメンテーション の関連部分を誰もまだ投稿していません:

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

223
glglgl

私はPythonインタプリタの内部動作について何も知りません(そして私はコンパイラやインタプリタのエキスパートでもありません)ので、私が無意味なことや不可能なことを提案しても私を責めないでください。

Pythonオブジェクト可変であるを仮定すれば、これはデフォルトの引数を設計するときに考慮に入れるべきだと思います。リストをインスタンス化するとき:

a = []

あなたはaによって参照されるnewリストを得ることを期待しています。

なぜa=[]

def x(a=[]):

呼び出しではなく、関数定義で新しいリストをインスタンス化しますか? 「ユーザーが引数を指定しない場合は インスタンス化 新しいリストを指定して、呼び出し側によって作成されたものとして使用する」というようなものです。私はこれが曖昧だと思う。

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

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

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

私は知っている、私が知っている:それは閉鎖です。あるいは、Pythonは定義時バインディングを強制するキーワードを提供するかもしれません。

def x(static a=b):
107
Utaal

その理由は、コードが実行されたときにバインディングが実行され、関数定義が実行されたときにバインディングが実行されるためです。

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

class BananaBunch:
    bananas = []

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

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

それは単なる "仕組み"であり、関数の場合に動作を変えるのはおそらく複雑であろうし、クラスの場合には不可能であろうし、あるいは少なくともオブジェクトのインスタンス化をかなり遅くするでしょうオブジェクトが作成されたらそれを実行します。

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

それはそれがどんな良いPythonチュートリアルでも際立って機能するべきであると言いました。あなたが言及したように、誰もが遅かれ早かれこの問題に遭遇するからです。

78
Lennart Regebro

実行時にオブジェクトを作成することがより良いアプローチであると私は考えていました。あなたがいくつかの便利な機能を失うので、私は今はっきりしません。そうすることの欠点は以下のとおりです。

1.パフォーマンス

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

呼び出し時評価が使用される場合、関数が引数なしで使用されるたびに高価な関数が呼び出されます。あなたはそれぞれの呼び出しに対して高価な代償を払うか、あるいは手動で値を外部的にキャッシュしてあなたの名前空間を汚染し冗長性を追加する必要があるでしょう。

2.束縛パラメータを強制する

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

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

これはそれぞれ0,1,2,3 ...を返す関数のリストを返します。振る舞いが変更されると、代わりにi call-time の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=[]

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

56
Brian

Pythonを守る5つのポイント

  1. 単純さ :この振る舞いは、次の意味で単純です。ほとんどの人は、このトラップに陥るのは一度だけです。

  2. 一貫性 :Python alwaysは名前ではなくオブジェクトを渡します。デフォルトのパラメータは、明らかに、関数の見出しの一部です(関数の本体ではありません)。したがって、関数呼び出し時ではなく、モジュールロード時(そしてネストされていない限り、モジュールロード時にのみ)に評価されるべきです。

  3. 有用性 :Frederik Lundhが彼の "Pythonのデフォルトパラメータ値" の説明で指摘しているように、現在の振る舞いは高度なプログラミングには非常に便利です。 (控えめに使ってください。)

  4. 十分なドキュメンテーション :最も基本的なPythonドキュメンテーション、チュートリアルでは、問題はセクション firstサブセクションの "Important warning" として大声で発表されています「関数定義の詳細」 。この警告では、見出しの外側に適用されることはめったにない太字も使用されています。 RTFM:すばらしいマニュアルを読んでください。

  5. メタラーニング :トラップに陥ることは実際には非常に役に立つ瞬間です(少なくともあなたが内省的な学習者であるなら)、あなたは後に上記の「一貫性」の点をよりよく理解するでしょうPythonについて扱います。

54
Lutz Prechelt

なぜあなたは内省しないのですか?

私は本当に)だれもが呼び出し可能オブジェクトに対してPythonが提供する洞察に満ちたイントロスペクション(2および3 apply)を実行していないことに驚いています。

次のように定義された単純な小さな関数funcname__を考えます。

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

Pythonがそれに遭遇したとき、最初にすることはこの関数のためのcodename__オブジェクトを作成するためにそれをコンパイルすることです。このコンパイルステップが完了している間、関数オブジェクト自体のPython 評価 *、次に stores デフォルトの引数(ここでは空のリスト[]。トップ答えとして、リストaname__これで、関数funcname__のmember)と見なすことができます。

それでは、リストがどのように展開されるのかを調べるために、前後にイントロスペクションを行いましょう 内部 関数オブジェクト。私はこれにPython 3.xを使っています。Python2にも同じことが言えます(Python 2では__defaults__またはfunc_defaultsを使います;そう、同じことに2つの名前を使います)。

実行前の機能

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

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

>>> func.__defaults__
([],)

O.kなので、__defaults__の中の単一のエントリとして空のリストができました。

実行後の機能

この関数を実行しましょう。

>>> func()

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

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

驚いた?オブジェクト内の値が変わります!関数への連続した呼び出しは、単にその埋め込まれたlistname__オブジェクトに単に追加されます。

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

それで、あなたはそれを持っています、この'flaw'が起こる理由は、デフォルト引数が関数オブジェクトの一部だからです。ここで奇妙なことは何もありません、それはすべて少しだけ驚くべきことです。

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

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

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


__defaults__のリストが関数funcname__で使用されているものと同じであることをさらに確認するには、関数本体内で使用されているリストidname__のaname__を返すように関数を変更するだけです。次に、それを__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()は、関数を構築してbarname__という名前にバインドするプロセスが行われる前に呼び出されます。

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

  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の変更 - 操作は同じオブジェクトに対して実行されます - そしてそれは表示されます
46
ymv

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

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)を呼び出す場合を除き、これは無視します。

言い換えれば、デフォルトのパラメータを評価するのではなく、それぞれを保存して、関数が呼び出されたときに評価するのはなぜでしょうか。

一つの答えはおそらくそこにあります - それは事実上デフォルトパラメータを持つすべての関数をクロージャに変えるでしょう。たとえすべてがインタプリタの中に隠されていて、本格的なクロージャではないとしても、データはどこかに保存されなければなりません。それは遅くなり、より多くのメモリを使用します。

33
Glenn Maynard

1)「Mutable Default Argument」のいわゆる問題は、一般に次のことを示す特別な例です。
「この問題を持つすべての関数は、実際のパラメーターで同様の副作用の問題も抱えています。」
それは関数型プログラミングの規則に反しており、通常は望ましくないため、両方を修正する必要があります。

例:

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]

Solution:acopy
絶対に安全な解決策は、copyまたはdeepcopy最初に入力オブジェクトを使用してから、コピーに対して何でもします。

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

多くの組み込み可変型には、some_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]

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

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

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

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

31
hynekcer

これは実際にはデフォルト値とは無関係ですが、あなたが可変のデフォルト値を持つ関数を書くとき、それはしばしば予期しない振る舞いとして現れます。

>>> 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は呼び出し元から渡される可変変数をmodificationすることです。このようなコードは、関数がappend_5のような名前で呼ばれていれば問題ありません。それから、呼び出し側は、渡された値を変更するために関数を呼び出すことになり、その動作は予想されます。しかし、そのような関数はデフォルトの引数を取ることはほとんどありませんし、おそらくリストを返さないでしょう(呼び出し側はすでにそのリストへの参照を持っているので、それはちょうど渡されたものです)。

デフォルトの引数を持つあなたのオリジナルのfooは、明示的に渡されたかデフォルト値を得たかに関わらず、aを変更してはいけません。コンテキスト/名前/ドキュメントから引数が変更されることになっていることが明らかでない限り、あなたのコードは変更可能な引数だけを残すべきです。ローカルのテンポラリとして引数として渡された可変値を使うことは、私たちがPythonにいるかどうか、そしてデフォルトの引数が含まれるかどうかにかかわらず、非常に悪い考えです。

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

28
Ben

これはパフォーマンスの最適化です。この機能の結果として、これら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で静的変数を実際に使用しましたか?)

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

25
Jason Baker

すでに話題になっているトピックですが、ここで読んだものから、次のことが内部でどのように機能するのかを理解するのに役立ちました。

def bar(a=[]):
     print id(a)
     a = a + [1]
     print id(a)
     return a

>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object 
[1]
>>> id(bar.func_defaults[0])
4484370232
25
Stéphane

最短の答えはおそらく「定義は実行」であろう、それ故に全体の議論は厳密な意味をなさない。もっと人為的な例として、あなたはこれを引用することができます:

def a(): return []

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

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

私はあなたがデフォルトのコンストラクタを使用しようとしたときには、しかしそれは落胆だと思います。

22
Baczek

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 __== '__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
20
Aaron Hall

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]
19
hugo24

次のことを考慮すると、この動作は驚くことではありません。

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

(2)の役割は、このスレッドで広範囲にカバーされています。 (1)は、他の言語から来たときにこの動作が「直感的」ではないため、おそらく驚異の原因となります。

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

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

元の例を振り返って、上記の点を考慮してください。

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

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

デフォルトを上書きせずにfooを呼び出すと、foo.func_defsのデフォルト値が使用されます。この場合、foo.func_defs[0]は、関数オブジェクトのコードスコープ内のaに使用されます。 aの変更foo.func_defs[0]を変更します。これはfooオブジェクトの一部であり、fooのコードの実行間で保持されます。

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

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)a Lという名前の新しいローカル変数ごとローカルスコープで作成され、関数呼び出しの残りに使用されます。したがって、foo.func_defs[0]は、将来のfooの呼び出しに対して変更されません。
19

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

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

2番目のオプションはNiceです。なぜなら、この関数のユーザは(typeのように)既に存在している可能性があるcallableを渡すことができるからです。

17
Marcin

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

他の人が広くコメントしているように、listパラメータは、実行時ではなく定義時に関数に結び付けられます。リストと辞書は変更可能であるため、このパラメータを変更すると、他のこの関数の呼び出しに影響を与えます。結果として、その後の関数呼び出しは、他の関数呼び出しによって変更された可能性があるこの共有リストを受け取ります。さらに悪いことに、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

Per Brett Slatkinの "効果的なPython:より良いPythonを書くための59の特定の方法"、 項目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]

プログラマがデフォルトのリストパラメータを共有することを意図した '間違ったメソッド'のための合法的なユースケースがあるかもしれませんが、これはおそらくルールよりも例外です。

17
Alexander

私は時々、この振る舞いを次のパターンの代替として悪用します。

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

singletonuse_singletonによってのみ使用される場合は、次のパターンが代わりに使用されます。

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

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

私はこのパターンがよく知られているとは思わないので、私は将来の誤解から守るために短いコメントを入れます。

16
bgreen-litl

これを回避するには、オブジェクトを置き換えます(したがって、スコープとTieを結び付けます)。

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

醜い、しかしそれはうまくいく。

15
jdborg

これを行うと、

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

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

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

def foo(a=pavlo):
   ...

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

pavloが変更可能(変更可能)で、fooがそれを変更することになった場合、次にfooが指定されずにaが呼び出されたときに、その効果がわかります。

だからこれはあなたが見るものです(pavloは[]に初期化されていることを忘れないでください):

 >>> foo()
 [5]

今、pavloは[5]です。

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

>>> foo()
[5, 5]

foo()を呼び出すときにaを指定すると、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]
15
Saish

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

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

上記の両方の機能を保持し、さらに別のポイントを示すことは完全に一貫しています。

  1. これは混乱を招く機能であり、Pythonでは残念です。

他の答え、または少なくともそれらのいくつかは、ポイント1と2を作るが3ではないか、ポイント3を作り、ポイント1と2を軽視します。しかし、3つとも真です。

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

既存の振る舞いはPythonicではなく、Pythonは成功します。これは、言語についてはほとんど驚かないという原則に違反するものがほとんどないためですnearthisひどく。根こそぎにするのが賢明であろうとなかろうと、それは本当の問題です。これは設計上の欠陥です。動作を追跡することで言語をよりよく理解している場合、C++はこれ以上のことをすべて行っていると言えます。たとえば、微妙なポインターエラーをナビゲートすることで多くのことを学びます。しかし、これはPythonicではありません:Pythonが他の言語よりもはるかに少ない驚きを持っているので、この振る舞いに直面しても耐えるのに十分なPythonを気にかける人です。ダブラーと好奇心の強い人は、何かを動かすのにどれほど時間がかからないことに驚いたときにPythonistasになります-設計flのためではなく-つまり、隠されたロジックパズル-これは、PythonなぜならJust Works

12

この「バグ」は私に多くの残業時間を与えました!しかし、私はそれが潜在的に使用されるのを見始めています(しかし、私はまだそれが実行時にあることを望んでいたでしょう)

私が役に立つ例として私が見るものをあなたに与えるつもりです。

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
9
Norfeldt

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

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

上記の2つの点を認めながら、Pythonコードに何が起きたのかを説明しましょう。これは、オブジェクトを参照渡しするためだけですが、可変/不変、または "def"ステートメントが定義されたときに1回だけ実行されるという事実とは無関係です。

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

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

======第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のコピーは1つしかありません。したがって、fooが呼び出されるたびに、itemsは(1度だけ評価されるという答えとは対照的に)Noneと評価され、明確にすると、Noneの参照(またはアドレス)と評価されます。次に、fooでは、itemは[]に変更されます。つまり、アドレスが異なる別のオブジェクトを指します。

======第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)を呼び出すと、項目はアドレス付きのリストobject []を指すようになります(11111111など)。リストの内容は、続編のfoo関数では 1 に変更されますが、アドレスは変更されません。それでもfoo(2、[])がやってくる。 foo(2、[])の[]は、foo(1)を呼び出すときのデフォルトパラメータ[]と同じ内容ですが、アドレスは異なります。明示的にパラメータを指定しているので、itemsはこの新しい[]のアドレスを取得しなければなりません、そして2222222と言って、そして何らかの変更を加えた後にそれを返してください。今foo(3)が実行されます。 xのみが提供されているので、itemsは再びデフォルト値を取らなければなりません。デフォルト値は何ですか? foo関数、つまり11111111にあるリストオブジェクトを定義するときに設定されます。したがって、項目は要素1を持つアドレス11111111になるように評価されます。2222222にあるリストにも要素2が1つ含まれますが、項目によって指されることはありません。もっと。したがって、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]()を実行すると、期待通りに7が得られ、x[9]()は9を返します。これはiの別の値です。

8
user2384994

これは設計上の問題ではありません 。これを乗り越えて誰もが何か悪いことをしている。

私はあなたがこの問題に出くわすかもしれないところで私が見る3つのケースがあります:

  1. あなたは関数の副作用として引数を変更しようとしています。この場合、 意味を成すことはありません デフォルト引数を持つことができます。唯一の例外は、引数リストが関数の属性を持つために悪用されている場合です。 cache={}を使用すると、実際の引数を使用して関数を呼び出すことはまったく期待できません。
  2. あなたは引数を変更しないでおくつもりですが、誤って した 変更しました。これはバグです。修正してください。
  3. 関数内で使用するために引数を変更しようとしていますが、その変更が関数外で表示されることを想定していませんでした。その場合、デフォルトのかどうかにかかわらず、引数の copy を作成する必要があります。 Pythonは値渡し(call-by-value)言語ではないので、コピーを作成することはできません。明示的に説明する必要があります。

問題の例は、カテゴリ1または3に分類されます。渡されたリストを変更し、それを返すことは変わっています。どちらかを選ぶべきです。

8
Mark Ransom

以下のように関数を変更するだけです。

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
7
ytpillai

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


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

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

def name部分must定義スコープで評価します-結局、nameをそこで利用可能にしたいのです。内部でのみ関数を評価すると、アクセスできなくなります。

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

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

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

2つの選択肢は同等ではありません。定義時にdefaultが評価された場合、はまだ実行可能ですdefaultが実行時に評価される場合、cannotは定義時間に影響します。 「定義時」を選択すると両方のケースを表現できますが、「実行時」を選択すると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
    ...
5
MisterMiyagi

他のすべての答えは、なぜこれが実際には素晴らしく望ましい動作であるのか、またはなぜこれを必要としないのかを説明します。私のものは、言語を自分の意思に合わせる権利を行使したい、頑固な人のためのものです。

デフォルト値のままの各位置引数に対して同じインスタンスを再利用するのではなく、デフォルト値をコピーするデコレータを使用してこの動作を「修正」します。

import inspect
from copy import copy

def sanify(function):
    def wrapper(*a, **kw):
        # store the default values
        defaults = inspect.getargspec(function).defaults # for python2
        # construct a new argument list
        new_args = []
        for i, arg in enumerate(defaults):
            # allow passing positional arguments
            if i in range(len(a)):
                new_args.append(a[i])
            else:
                # copy the value
                new_args.append(copy(arg))
        return function(*new_args, **kw)
    return wrapper

それでは、このデコレータを使って関数を再定義しましょう。

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

foo() # '[5]'
foo() # '[5]' -- as desired

これは、複数の引数を取る関数には特に便利です。比較しなさい:

# the 'correct' approach
def bar(a=None, b=None, c=None):
    if a is None:
        a = []
    if b is None:
        b = []
    if c is None:
        c = []
    # finally do the actual work

# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
    # wow, works right out of the box!

キーワードargsを使用しようとすると、上記の解決策はうまくいかないことに注意することが重要です。

foo(a=[4])

デコレータはそれを可能にするように調整することができますが、私たちはこれを読者のための練習として残します;)

0
Przemek D