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()
私のマネージャーの 1 人がこの機能に初めて遭遇したとき、これを言語の「重大な設計上の欠陥」と呼びました。私は、この動作には根本的な説明があり、内部を理解していない場合、非常に不可解で予期しない動作であると答えました。しかし、次の質問には (自分自身に) 答えられませんでした。関数の実行時ではなく、関数定義時にデフォルト引数をバインドする理由は何ですか? 経験的な動作に実用的な用途があるとは思えません (バグを発生せずに C で静的変数を実際に使用した人はいますか?)
編集:
バチェク氏は興味深い例を挙げた。皆さんのコメントのほとんどと合わせて特にウタールの私はさらに詳しく説明しました。
def a():
print("a executed")
return []
def b(x=a()):
x.append(5)
print(x)
a executed
>>> b()
[5]
>>> b()
[5, 5]
私には、設計上の決定は、パラメータのスコープを関数内に置くか、それとも関数と一緒に置くかということに関連しているように思えます。
関数内でバインディングを行うと、x
関数が呼び出されるときに、指定されたデフォルトに事実上バインドされ、定義されないことになり、深刻な欠陥が生じます。def
つまり、バインディングの一部 (関数オブジェクトの) は定義時に行われ、一部 (デフォルト パラメータの割り当て) は関数の呼び出し時に行われるという意味で、この行は「ハイブリッド」になります。
実際の動作はより一貫しており、その行が実行されるとき、つまり関数定義時に、その行のすべてが評価されます。
ベストアンサー1
実際、これは設計上の欠陥ではなく、内部やパフォーマンスの問題でもありません。これは単に、Python の関数が単なるコードではなく、ファーストクラスのオブジェクトであるという事実から生じています。
このように考えると、完全に意味がわかります。関数は定義に基づいて評価されるオブジェクトです。デフォルト パラメータは一種の「メンバー データ」であるため、他のオブジェクトとまったく同じように、呼び出しごとに状態が変化する可能性があります。
いずれにせよ、effbot(Fredrik Lundh)は、この動作の理由について非常にうまく説明しています。Python のデフォルトパラメータ値非常にわかりやすい内容でしたので、関数オブジェクトがどのように動作するかをより深く理解するために、ぜひ読んでみることをお勧めします。