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


2594

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
私は、変更可能な議論が平均的な人の最小の驚きの原則に違反していることに疑いはなく、初心者がそこに足を踏み入れた後、メーリングリストをメーリングタプルに英雄的に置き換えました。それでもなお、可変引数はPython Zen(Pep 20)に沿っており、「オランダ語には明らか」(ハードコアのPythonプログラマーによって理解/活用されている)条項に該当します。doc文字列を使用した推奨される回避策は最良ですが、doc文字列と(書かれた)ドキュメントへの抵抗は、今日ではそれほど珍しくありません。個人的には、私はデコレーター(@fixed_defaultsなど)を好みます。
セルジュ

5
これに遭遇したときの私の意見は、「関数に渡すミュータブルである可能性のあるミュータブルを返す関数を作成する必要があるのはなぜですか?ミュータブルを変更するか、新しいものを作成します。なぜ必要なのですか?両方を1つの関数で実行するにはどうすればよいですか?また、コードに3行追加することなくそれを実行できるようにインタープリターを書き直す必要があるのはなぜですか?」ここでは、インタプリタが関数の定義と呼び出しを処理する方法を書き換えることについて話しているからです。かろうじて必要なユースケースでは、これは多くのことを行います。
アランロイタード2017年

12
「Python初心者は、この関数が常に1つの要素のみを含むリストを返すことを期待します[5]。」私は、Pythonの初心者だし、明らかにので、私は、このことを期待していないfoo([1])が返されます[1, 5]、ではありません[5]。あなたが言うつもりだったことは、初心者はパラメータなしで呼び出された関数が常に戻ることを期待するということ[5]です。
symplectomorphic

2
この質問は、「なぜこれが[間違った方法で]実装されたのですか?」それは聞いていない「正しい方法は何?」[ arg = Noneを使用すると、Pythonの可変デフォルト引数の問題が修正されるのはなぜですか?] *(stackoverflow.com/questions/10676729/…)。新規ユーザーはほとんど常に前者への関心が低く、後者への関心がはるかに高いため、引用するのに非常に役立つリンク/重複である場合があります。
smci

回答:


1612

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

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

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


80
上記の回答を読んでいる人には、リンクされたEffbotの記事を読むことを強くお勧めします。他のすべての有用な情報と同様に、この言語機能を結果のキャッシング/メモ化にどのように使用できるかについての部分は、知っておくと非常に便利です!
カムジャクソン

85
それがファーストクラスのオブジェクトである場合でも、各デフォルト値のコードがオブジェクトとともに格納され、関数が呼び出されるたびに再評価される設計を思い描くかもしれません。ファーストクラスのオブジェクトである関数が完全に排除するわけではないというだけで、それがより良いとは言っていません。
gerrit 2013年

312
申し訳ありませんが、「Pythonで最大のWTF」と見なされるものは、間違いなく設計上の欠陥です。誰もが最初はその動作を期待していないため、これはにとってバグの原因となります。つまり、そもそもそのような方法で設計されるべきではなかったのです。私は彼らがジャンプしなければならなかったどんなフープも気にしません、彼らデフォルト引数が非静的であるようにPythonを設計するべきでした。
BlueRaja-Danny Pflughoeft 2013年

192
それが設計上の欠陥であるかどうかに関係なく、あなたの答えは、関数がファーストクラスのオブジェクトであることを考えると、この振る舞いが何らかの形で必要であり、自然で明白であることを暗示しているようです。Pythonにはクロージャーがあります。デフォルトの引数を関数の最初の行の割り当てに置き換えると、呼び出しごとに式が評価されます(囲まれたスコープで宣言された名前を使用している可能性があります)。関数がまったく同じ方法で呼び出されるたびにデフォルトの引数を評価することが不可能または合理的ではないという理由はまったくありません。
Mark Amery 2014年

24
デザインはから直接従うものではありませんfunctions are objects。あなたのパラダイムでは、提案は属性ではなくプロパティとして関数のデフォルト値を実装することです。
ブクソール2014年

273

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

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の現在のアプローチが好きです。デフォルトの関数引数は、関数が定義されたときに評価され、そのオブジェクトは常にデフォルトです。私は彼らが空のリストを使用して特別な場合があるかもしれないと思いますが、そのような特別なケーシングは後方互換性は言うまでもなく、さらに驚くべきことを引き起こします。


30
それは議論の問題だと思います。あなたはグローバル変数に作用しています。グローバル変数を含むコード内のどこかで実行された評価は(正しく)(「blueberry」、「mangos」)を参照するようになります。デフォルトのパラメータは、他のケースと同じようにすることができます。
Stefano Borini、

47
実際、私はあなたの最初の例に同意するとは思いません。そもそもそのようなイニシャライザを変更するアイデアが好きかどうかはわかりませんが、そうした場合、デフォルト値をに変更することで、説明どおりに動作することが期待されます("blueberries", "mangos")
ベンブランク

12
デフォルトのパラメーター他の場合と同様です。予期しないことは、パラメーターがローカル変数ではなくグローバル変数であることです。これは、コードが呼び出しではなく関数定義で実行されるためです。それが得られたら、それはクラスでも同じです。それは完全に明らかです。
Lennart Regebro 2009

17
私は見事なものではなく、誤解を招く例を見つけました。場合some_random_function()に追加fruitsの代わりがそれに割り当てる、の動作がeat() します変更します。現在の素晴らしいデザインについては以上です。他の場所で参照されているデフォルトの引数を使用し、関数の外部から参照を変更すると、問題が発生します。本当のWTFは、人々が新しいデフォルトの引数(リストリテラルまたはコンストラクターへの呼び出し)を定義し、それでもビットを取得するときです。
アレクシス2014年

13
globalタプルを明示的に宣言して再割り当てしただけeatです。その後、別の方法で動作しても、驚くことはありません。
user3467349

241

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

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

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

180
「これは一般的に意図されたものではない」および「これを回避する方法は」というフレーズは、設計上の欠陥を文書化しているようなにおいがします。
ブクソール2014年

4
@マシュー:私はよく知っていますが、落とし穴に値するものではありません。このため、一般に、スタイルガイドとリンターが無条件に変更可能なデフォルト値に誤ったフラグを立てます。同じことをするための明示的な方法は、属性を関数(function.data = [])に詰め込むか、オブジェクトを作成することです。
ブクソール2014年

6
@bukzor:落とし穴に注意して文書化する必要があります。このため、この質問は適切であり、非常に多くの賛成票を集めています。同時に、落とし穴は必ずしも削除する必要はありません。何人のPython初心者がリストを変更した関数にリストを渡し、元の変数に変更が表示されるのを見てショックを受けましたか?しかし、変更可能なオブジェクト型は、その使用方法を理解していると素晴らしいものになります。私はそれがこの特定の落とし穴に関する意見に要約されると思います。
マシュー14年

33
「これは一般的に意図されたものではない」という表現は、「プログラマーが実際に実現したいと思ったことではなく」、「Pythonが実行するはずのことではない」という意味です。
holdenweb 2014

4
@holdenwebうわー、私はパーティーに大遅れです。コンテキストを考えると、bukzorは完全に正しいです。つまり、言語が関数の定義を実行する必要があると判断したときに「意図した」ものではない動作/結果を文書化しています。彼らのデザイン選択の意図しない結果であるので、それはデザインの欠陥です。それが設計上の欠陥でなければ、「これを回避する方法」を提供する必要すらありません。
code_dredd 2017年

118

私は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):

11
A = datetime.datetime.now()設定なし、ではない場合、そして:DEF X(=なし):あなたは何ができる
アノン

20
これありがとう。なぜこれが私をいらいらさせるのか、私は実際に私の指を置くことができませんでした。最小限のファズと混乱でそれを美しく行いました。C ++でのシステムプログラミングと、時には単純な「翻訳」言語機能から生まれた誰かとして、この偽の友人は、クラス属性と同じように、頭の中で非常に柔らかくなった。なぜこうなるのかは理解できますが、どんなにポジティブなものになっても、嫌いにならざるを得ません。少なくともそれは私の経験に反しているので、おそらく(うまくいけば)決して忘れないでしょう...
AndreasT

5
@Andreas Pythonを十分に長く使用すると、Pythonが物事をクラス属性として解釈することがいかに論理的であるかを理解し始めます。これは、C ++(およびJavaなどの言語の特定の癖および制限のためです) C#...)の内容に意味があること class {}ブロックのインスタンスに属するものとして解釈されるの :)クラスがファーストクラスのオブジェクトである場合、当然のことながら、コンテンツ(メモリ内)がコンテンツを反映するのは当然です。 (コード内)。
Karl Knechtel、2011

6
私の本では、規範的な構造は奇抜でも制限でもありません。不器用で醜いものになることは承知していますが、何かの「定義」と呼んでもかまいません。動的言語は私にとってはアナキストのように見えます。もちろん誰もが自由ですが、誰かがゴミを空にして道路を舗装するための構造が必要です。私は古いと思います... :)
AndreasT

4
関数の定義は、モジュールのロード時に実行されます。関数本体は関数呼び出し時に実行されます。デフォルトの引数は、関数本体ではなく、関数定義の一部です。(ネストされた関数の場合は複雑になります。)
Lutz Prechelt

84

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

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

class BananaBunch:
    bananas = []

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

このコードは、まったく同じ予期せぬ事態の影響を受けます。bananasはクラス属性であるため、それに属性を追加すると、そのクラスのすべてのインスタンスに追加されます。理由はまったく同じです。

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

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

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


クラスのインスタンスごとに異なるクラス属性をどのように定義しますか?
キエーヴェリ2009

19
インスタンスごとに異なる場合は、クラス属性ではありません。クラス属性は、CLASSの属性です。したがって、名前。したがって、それらはすべてのインスタンスで同じです。
Lennart Regebro、2009

1
クラスのインスタンスごとに異なるクラスの属性をどのように定義しますか?(Pythonの命名規則に慣れていない人がクラスの通常のメンバー変数について尋ねている可能性があると判断できなかった人のために再定義されています)。
キエーヴェリ2009

@Kievieli:あなたはクラスの通常のメンバー変数について話している。:-)任意のメソッドでself.attribute = valueと言ってインスタンス属性を定義します。たとえば、__ init __()です。
Lennart Regebro 2009

@Kieveli:2つの答え:できません。クラスレベルで定義したものはすべてクラス属性になり、その属性にアクセスするインスタンスは同じクラス属性にアクセスするためです。/ sort of /を使用すると、propertys を使用できます。これは、実際には通常の属性のように機能するが、クラスではなくインスタンスに属性を保存するクラスレベルの関数です(self.attribute = valueLennartによると)。
イーサンファーマン2012年

66

なぜ内省しませんか?

Pythonが提供する洞察に満ちたイントロスペクションを誰も実行していないことに本当に驚いています2そして3呼び出し可能オブジェクトに適用されますが)。

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

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

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

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

実行前の機能:

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

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

>>> func.__defaults__
([],)

では、空のリストを1つのエントリとして __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関数を変更して、関数本体内で使用されてidいるリストのを返すだけaです。その後、内のリストにそれを比較する__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れます。


1
されてid(...)いる最後の検証のために必要な、またはでしょうisオペレータは、同じ質問に答えますか?
das-g

1
@ das-g isは問題なく動作しid(val)ます。より直感的になる可能性があると思うので、使用しました。
Dimitris Fasarakis Hilliard

Noneデフォルトとして使用すると、__defaults__イントロスペクションの有用性が大幅に制限されるため、これが適切に機能することの防御策としてはうまく機能しないと思います__defaults__。遅延評価は、関数のデフォルトを両側から有用に保つためにより多くのことを行います。
ブリリアント

58

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

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=[]

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


1
値の代わりにデフォルトの引数を作成する関数があったとしても、イントロスペクションを実現できます。inspectモジュールはその関数を呼び出すだけです。
yairchu 2009

@SilentGhost:私はそれを再作成するために動作が変更されたかどうかについて話しています-一度作成することが現在の動作であり、なぜ変更可能なデフォルトの問題が存在するのですか?
ブライアン、

1
@yairchu:これは、構築が安全である(つまり、副作用がない)ことを前提としています。argをイントロスペクトすること何もすべきではありません、任意のコードを評価することは結局効果をもたらすことになります。
ブライアン

1
多くの場合、異なる言語設計は、物事を異なる方法で書くことを意味します。最初の例は次のように簡単に書くことができます:_expensive = expensive(); def foo(arg = _expensive)、特に再評価したくない場合。
グレンメイナード

@Glenn-それは私が「変数を外部にキャッシュする」で参照していたものです-それは少し冗長で、名前空間に余分な変数が含まれることになります。
ブライアン

55

Pythonを守る5つのポイント

  1. 単純さ:この動作は、次の意味で単純です。ほとんどの人がこのトラップに陥るのは1回だけで、数回ではありません。

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

  3. 有用性:Frederik Lundhが「Pythonのデフォルトパラメータ値」の説明で指摘しているように、現在の動作は高度なプログラミングに非常に役立ちます。(控えめに使用してください。)

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

  5. メタ学習:トラップに陥るのは、実際には非常に役立つ瞬間です(少なくとも反射的な学習者の場合)。その後、上記の「一貫性」のポイントをよりよく理解し、Pythonについて多くを学ぶことができるからです。


18
この動作が本番環境でコードをめちゃくちゃにするのに1年かかりました。偶然にこの設計上の欠陥にぶつかるまで、完全な機能を削除してしまいました。私はDjangoを使用しています。ステージング環境には多くのリクエストがなかったため、このバグがQAに影響を与えることはありませんでした。ライブになり、多くの同時リクエストを受け取ったとき-いくつかのユーティリティ関数がお互いのパラメーターを上書きし始めました!セキュリティホール、バグなどを作ります。
oriadam

7
@oriadam、違反はありませんが、これまでに遭遇せずにPythonをどのように学んだのでしょうか。私は今Pythonを学習しているだけです。この潜在的な落とし穴は、デフォルトの引数の最初の言及と並んで、公式のPythonチュートリアルで言及されています。(この回答のポイント4で述べたように。)私は、教訓は-むしろ同情せずに- 生産ソフトウェアの作成に使用する言語の公式ドキュメントを読むことだと思います。
ワイルドカード

また、私が行っている関数呼び出しに加えて、未知の複雑さの関数が呼び出された場合、それは(私にとって)驚くべきことです。
Vatine

52

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

  1. 関数(クラスなど)宣言は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 変更-操作は同じオブジェクトで実行され、印刷されます

(実際には、addは悪い例ですが、整数が不変であることは私の主なポイントです。)
Anon

bを[]に設定すると、b .__ add __([1])は[1]を返しますが、リストが変更可能であってもbをそのまま[]のままにすることを確認した後、私の悔しさにそれを実現しました。私の悪い。
Anon、

@ANon:はありますが__iadd__、intでは機能しません。もちろん。:-)
Veky

35

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

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


6
それはクロージャーである必要はありません-それを考えるより良い方法は単純にバイトコードを作成することをコードの最初の行にすることです-結局のところ、とにかくその時点で本体をコンパイルしている-コード間に本当の違いはありません引数と本文のコードで。
ブライアン

10
そうですが、それでもPythonの速度は低下します。クラス定義に対して同じことを行わない限り、実際には驚くべきことです。インスタンス化するたびにクラス定義全体を再実行する必要があるため、それは愚かに遅くなります。クラス。述べたように、修正は問題よりも意外です。
Lennart Regebro、2009

レナルトに同意する。Guidoが好きなように、すべての言語機能または標準ライブラリについて、それを使用しているがいます。
Jason Baker、

6
今それを変えることは狂気になるでしょう-私たちはそれがなぜそれがそうであるのか理由を探っているだけです。それが遅れてデフォルト評価を始めたとしても、それは必ずしも驚くべきことではありません。このようなコアと解析の違いが言語全体に影響を及ぼし、おそらく多くのあいまいな影響を与えることは間違いありません。
グレンメイナード

35

: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]"

多くの組み込み変更可能なタイプは次のようにコピーする方法を持っている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]

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

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

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

3)場合によっては、デフォルトパラメータの変更可能な動作が役立ちます。


5
Pythonが関数型プログラミング言語ではないことをご承知おきください。
Veky 2014年

6
はい、Pythonはいくつかの機能的な機能を備えたマルチパラグラム言語です。(「ハンマーがあるからといってすべての問題を釘のように見せないでください。」)それらの多くはPythonのベストプラクティスにあります。Pythonには興味深いHOWTO関数型プログラミングがあります。その他の機能は、ここでは触れませんが、クロージャーとカリー化です。
hynekcer 2014年

1
また、この後半の段階で、Pythonの割り当てセマンティクスは必要に応じてデータのコピーを回避するように明示的に設計されているため、コピー(特にディープコピー)の作成はランタイムとメモリ使用量の両方に悪影響を及ぼします。したがって、それらは必要な場合にのみ使用する必要がありますが、新規参入者はそれがいつであるか理解するのが難しい場合があります。
holdenweb 2018年

1
@holdenweb同意する。一時的なコピーは、元の変更可能なデータを潜在的に変更する無関係な関数から保護する最も一般的な方法であり、唯一の可能な方法です。幸いなことに、データを不当に変更する関数はバグと見なされるため、一般的ではありません。
hynekcer

私はこの答えに同意します。そしてdef f( a = None )、あなたが本当に他のことを意味しているのに、なぜその構造が推奨されるのか理解できません。引数を変更するべきではないため、コピーは問題ありません。とするとif a is None: a = [1, 2, 3]、とにかくリストをコピーします。
koddo

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明示的に渡されたか、デフォルト値を取得したかを。引数が変更されることになっていることがcontext / name / documentationから明らかでない限り、コードは変更可能な引数をそのままにする必要があります。引数として渡された可変値をローカル一時変数として使用することは、Pythonを使用しているかどうかに関係なく、デフォルトの引数が含まれているかどうかに関係なく、非常に悪い考えです。

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


7
関連はありますが、これは明確な動作だと思います(「インプレース」でのappend変更が予想されるためa)。デフォルトの可変は、呼び出しごとに再インスタンス化されていないことは、少なくとも私のために...「予想外」ビットです。:)
アンディ・ヘイデン

2
@AndyHayden関数が引数を変更することが予想される場合、デフォルトを持つことが理にかなっているのはなぜですか?
Mark Ransom

@MarkRansomは、私が考えることができる唯一の例ですcache={}。ただし、この「最小の驚き」は、引数を変更するために呼び出している関数を期待していない(または望んでいない)場合に発生すると思います。
アンディヘイデン

1
@AndyHayden私はここに自分の答えを残し、その感情を拡大しました。どう考えているか教えてください。cache={}完全を期すために、あなたの例を追加します。
Mark Ransom

1
@AndyHayden私の回答の要点は、引数のデフォルト値を誤って変更して驚いた場合、別のバグがあり、デフォルト使用されていないときにコードが呼び出し元の値を誤って変更する可能性があるということです。またNone、argが実際のデフォルトを使用して割り当ててNone も問題が解決しないことに注意してください(その理由から、これはアンチパターンと見なされます)。デフォルトがあるかどうかに関係なく、引数の値を変更しないようにして他のバグを修正すると、この「驚くべき」動作に気付くことも気にすることもありません。
ベン

27

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

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

2
実際、これはa = a + [1]オーバーロードとして新規参入者を少し混乱させるかもしれませんa...に変更してb = a + [1] ; print id(b)行を追加することを検討してa.append(2)ください。これ+により、2つのリストでは常に新しいリスト(に割り当てられているb)が作成されaますが、変更されたリストでも同じが可能id(a)です。
ジョーンHEES

25

これはパフォーマンスの最適化です。この機能の結果、これら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では、この利点はありません。


24

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

24

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

def a(): return []

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

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

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


20

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

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

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

(2)の役割は、このスレッドで広くカバーされています。(1)他の言語から来た場合、この動作は「直感的」ではないため、おそらく驚異を引き起こす要因です。

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

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

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

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]で使用されaます。オブジェクトの一部であり、でコードを実行するまで持続するachange への変更。foo.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

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。

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

ここでのソリューションは次のとおりです。

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

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


16

これを行うと:

def foo(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]

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()

これは、外部リソースにアクセスするクライアントクラスをインスタンス化するために使用したり、メモ用のディクショナリやリストを作成したりするためにも使用しました。

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


2
私はメモ化のためにデコレータを追加し、メモ化キャッシュを関数オブジェクト自体に置くことを好みます。
Stefano Borini、2015

この例は_make_singleton、デフォルトの引数の例ではdef時に呼び出しますが、グローバルな例では呼び出し時に呼び出すため、表示するより複雑なパターンに代わるものではありません。真の置換では、デフォルトの引数値にある種の可変ボックスを使用しますが、引数を追加すると、代替値を渡す機会が与えられます。
Yann Vernier 2017年

15

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

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

醜いですが、動作します。


3
これは、自動ドキュメント生成ソフトウェアを使用して、関数が期待する引数のタイプをドキュメント化する場合に適したソリューションです。a = Noneを指定してから、aが[]の場合にaを[]に設定しても、期待される内容を一目で理解するのに役立ちません。
Michael Scott Cuthbert

クールなアイデア:その名前を再バインドすると、変更できないことが保証されます。本当に好きです。
holdenweb 2018年

これはまさにそれを行う方法です。Pythonはパラメータのコピーを作成しないため、明示的にコピーを作成する必要があります。コピーを作成したら、予期しない副作用なしに、自由に変更できます。
Mark Ransom

13

それは本当かもしれない:

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

上記の両方の機能を保持し、それでも別のポイントを作ることは完全に一貫しています:

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

他の回答、または少なくともそれらのいくつかは、ポイント1と2を作成して3を作成しないか、ポイント3とダウンポイントを1と2に作成します。しかし、3つすべてが真です。

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

既存の振る舞いはPythonicではなく、Pythonは成功しています。なぜなら、言語についてのほとんどのところ、どこにも驚かないという原則に違反しないからです。 近くこれはひどい。それを根こそぎにすることが賢明であるかどうかにかかわらず、それは本当の問題です。設計上の不具合です。動作を追跡することで言語をよりよく理解できれば、C ++はこれ以上のすべてを行うと言えます。たとえば、微妙なポインタエラーをナビゲートすることで多くのことを学びます。しかし、これはPythonicではありません。この振る舞いに耐えるのに十分なほどPythonに関心がある人は、その言語に惹かれている人です。なぜなら、Pythonの驚きは他の言語よりもはるかに少ないからです。ダブラーと好奇心が強い人は、Pythonに惹かれるプログラマーの直感を損なう、設計FL(つまり、隠されたロジックパズル)のためではなく、何かを実行するのにかかる時間がどれだけ短いかに驚いたときに、Pythonistaになります。それだけで動作します。


6
-1防御の観点、これではない答えが、私はそれに反対します。特別な例外が多すぎると、独自のコーナーケースが発生します。
Marcin

3
それでは、Pythonで関数が呼び出されるたびに[]のデフォルト引数を[]のままにしておくほうが理にかなっていると言うのは「驚くほど無知」です。
クリストスヘイワード

3
そして、デフォルトの引数をNoneに設定することを不幸なイディオムと見なすことは無知であり、引数== Noneの場合、関数設定の本体の本文で:argument = []?世間知らずの初心者が期待することを望む人が多いので、このイディオムを不幸だと考えることは無知ですか?f(argument = [])を割り当てると、引数は自動的に[]の値にデフォルト設定されますか?
Christos Hayward

3
しかし、Pythonでは、言語の精神の一部は、あまり深く掘り下げる必要がないことです。array.sort()は機能し、ソート、big-O、および定数について理解していなくても機能します。配列のソートメカニズムにおけるPythonの優れた点は、数え切れないほどの例の1つを示すと、内部を深く掘り下げる必要がないことです。言い方を変えれば、Pythonの優れた点は、通常は、正しく動作するものを取得するために実装を深く掘り下げる必要がないことです。そして回避策があります(... if引数==なし:引数= [])、失敗。
Christos Hayward

3
スタンドアロンとしてのステートメントx=[]は、「空のリストオブジェクトを作成し、それに名前 'x'をバインドする」ことを意味します。したがって、def f(x=[])では、空のリストも作成されます。常にxにバインドされるとは限らないため、代わりにデフォルトのサロゲートにバインドされます。後でf()が呼び出されると、デフォルトが引き出され、xにバインドされます。リスにされたのは空のリスト自体だったので、何かがスタックされていてもいなくても、同じリストがxにバインドできる唯一のものです。それ以外の場合はどうでしょうか?
Jerry B

10

これは設計上の欠陥ではありません。これをトリップする人は誰でも何か間違ったことをしています。

この問題が発生する可能性のある場所が3つあります。

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

質問の例は、カテゴリ1または3に該当する可能性があります。渡されたリストを変更して返すことは奇妙です。どちらかを選択する必要があります。


「何かがおかしい」が診断です。とは言っても、= Noneパターンが有用だったときもあると思いますが、その場合、変更可能なものを渡した場合、通常は変更する必要はありません(2)。cache={}パターンは本当にあなたがおそらくしたい実際のコードでは面接のみのソリューションです@lru_cache
アンディヘイデン

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

8

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

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

7

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

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

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

[]はオブジェクトなので、Pythonは[]への参照をに渡します。aつまり、aメモリ内にオブジェクトとして存在する[]へのポインタにすぎません。[]のコピーは1つしかありませんが、多くの参照があります。最初のfoo()では、リスト[]がappendメソッドによって1に変更されます。ただし、リストオブジェクトのコピーは1つしかなく、このオブジェクトは1になることに注意してください。2番目のfoo()を実行すると、effbot Webページが言う(アイテムはもう評価されない)のが間違っています。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のコピーが1つしかありません。したがって、fooが呼び出されるたびに、項目は(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この新しいアドレスを取得する必要があります[]、たとえば2222222を取得し、いくつかの変更を行った後にそれを返す必要があります。ここでfoo(3)が実行されます。以来xが提供されている場合、アイテムは再びデフォルト値を使用する必要があります。デフォルト値は何ですか?これは、foo関数(11111111にあるリストオブジェクト)を定義するときに設定されます。したがって、アイテムは、要素1を持つアドレス11111111であると評価されます。2222222にあるリストにも1つの要素2が含まれていますが、アイテムによってポイントされていませんもっと。したがって、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


5
あなたの最後のポイントは間違っています。それを試してみて、あなたはそれが表示されますx[7]()です9
Duncan

2
「Pythonは基本データ型を値で渡します。つまり、値のローカルコピーをローカル変数に作成します」は完全に正しくありません。誰かが明らかにPythonをよく知っているにも関わらず、ファンダメンタルズについてそのような恐ろしい誤解を持っていることを私は驚いています。:-(
Veky 2014年

6

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
    ...

「整合性はすでに「def <name>(<args=defaults>):定義どおり」と言っています。それ以外のすべては定義でも最もよく評価されます。」結論が前提に基づいているとは思いません。2つのものが同じ行にあるからといって、それらが同じスコープで評価される必要があるという意味ではありません。default他の行とは異なります。それは式です。式の評価は、関数の定義とは非常に異なるプロセスです。
LarsH

@LarsH関数の定義がされている Pythonで評価しました。それがステートメント(def)または式()のどちらからであってもlambda、関数の作成は、特にそのシグニチャーの評価を意味することを変更しません。また、デフォルトは関数のシグネチャの一部です。これは、デフォルトすぐに評価する必要があるという意味ではありません。たとえば、型ヒントはそうではない場合があります。しかし、そうすべきでない正当な理由がない限り、そうすべきであるということは確かです。
MisterMiyagi

関数を作成するということは、ある意味での評価を意味しますが、関数内のすべての式が定義時に評価されるという意味ではありません。ほとんどはそうではありません。関数本体が「評価」される(適切な表現に解析される)よりも、定義時にシグネチャが特に「評価」されるのは、どのような意味であるのかがはっきりしません。一方、関数本体の式は、完全な意味では明らかに評価されません。この観点から、一貫性は、署名の式も「完全に」評価されるべきではないと言うでしょう。
LarsH

私はあなたが間違っているという意味ではなく、あなたの結論が一貫性だけから続くものではないというだけです。
-LarsH

@LarsHデフォルトはボディの一部ではなく、一貫性が唯一の基準であると私は主張していません。答えを明確にする方法を提案できますか?
MisterMiyagi

3

他のすべての答えは、これが実際に望ましい望ましい動作である理由、またはとにかくこれを必要とすべきではない理由を説明しています。私のものは、言語を自分の意思で曲げる権利を行使したい、その逆ではない頑固な人たちのためのものです。

この動作は、デフォルト値のままになっている位置引数ごとに同じインスタンスを再利用する代わりに、デフォルト値をコピーするデコレータで「修正」します。

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!

次のようにキーワード引数を使用しようとすると、上記のソリューションが機能しなくなることに注意することが重要です。

foo(a=[4])

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.