Python関数のデフォルトパラメータの設計【オリジナル】
6895 ワード
Pythonチュートリアルでは、デフォルトのパラメータについて、「重要な警告」の例を示します.
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
デフォルト値は一度しか実行されず、理由も言われていません.結果が印刷されます.
[1]
[1, 2]
[1, 2, 3]
最初の言語はルビーなので、ちょっと変な感じがします.しかし、方法fは変数Lを必ず格納しているに違いない.
準備知識:ポインタ
pは、数値などの可変オブジェクトを指します.pポインタが異なるメモリアドレスを指していることに相当します.
pはlistなどの可変オブジェクトを指します.list自体の変更は、listオブジェクト自体が存在するメモリアドレスを変更することはありません.したがって、pが指すメモリアドレスは変更されません.
>>> p = 1
>>> id(p)
4523801232
>>> p = p + 1
>>> id(p)
4523801264
>>> p = 11
>>> id(p)
4523801552
>>> p = []
>>> id(p)
4527080640
>>> p.append(11)
>>> id(p)
4527080640
根本的な原因.
Python関数のパラメータのデフォルト値は、コンパイル段階でバインドされます.(コードを書くときに定義しました.)
Python Common Gotchasから抜粋した理由を説明します.
Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.
これにより、
第三条、上記の例を修正する.
def f(a, L = 1):
L = a
print(id(L))
return L
print("self",id(f.__defaults__[0]))
print(f(1))
print("self",id(f.__defaults__[0]))
print(f(33))
print("self",id(f.__defaults__[0]))
# :
self 4353170064
4353170064
1
self 4353170064
4353171088
33
self 4353170064
デフォルトパラメータLは、コンパイルフェーズでバインドされ、__に格納されます.default__で行ないます.関数内のL=a式は、新しい変数を生成します.返されるLは、デフォルトのパラメータに関係なく、新しい変数です.
4つ目は、上記の例で、デフォルトパラメータのタイプを可変オブジェクトリストに変更します.
def f(a, L = []):
L.append(a)
print(id(L))
return L
# L = f(1)
print("self",id(f.__defaults__[0]))
print(f(1))
print("self",id(f.__defaults__[0]))
print(f(33))
print("self",id(f.__defaults__[0]))
#
self 4337586048
4337586048
[1]
self 4337586048
4337586048
[1, 33]
self 4337586048
id番号から分かるように、デフォルトパラメータ自体が返されます.
この罠を避けるために面倒をかける必要はありません
def f(a, L = None):
if L is None:
L = []
L.append(a)
return L
どうしてPythonがこんなデザインしたの?
StackOverflowでは議論が多い.
Rubyがこの問題を持たないのは,Rubyのdefキーワードが閉じた役割ドメインを定義しているためであり,どのデータもパラメータを介してメソッド内に転送しなければならないためであると考えられる.
Rubyに比べてPythonパラメータの役割は大きく弱められた.Pythonの登場はRubyより遅く、その創始者はRubyのデザインを参考にしたに違いない.この設計を捨ててjavascriptのような関数方式を選択した.def定義の関数は,実行時に外部作用ドメインの変数を受信できる.
次のような見方があります.
Pythonコンパイラの実装を考慮すると、関数は内部の1級オブジェクトです.パラメータのデフォルト値は、このオブジェクトのプロパティです.他の言語では、オブジェクトプロパティはオブジェクトの作成時にバインドされます.したがって、関数パラメータのデフォルト値がコンパイル時にバインドされるのも不思議ではありません.
以下を参照してください.http://cenalulu.github.io/python/default-mutable-arguments/#toc1と、自分の理解を加えた.転載を歓迎します!