32

配列パラメーターを使用してPythonで関数を定義する場合、そのパラメーターのスコープは何ですか?

この例は、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]

ここで何が起こっているのか理解できるかどうかはよくわかりません。これは、配列のスコープが関数の外にあることを意味しますか?配列が呼び出しごとに値を記憶しているのはなぜですか?他の言語から来ているので、変数が静的である場合にのみこの動作を期待します。それ以外の場合は、毎回リセットする必要があるようです。そして実際、私が次のことを試したとき:

def f(a):
    L = []
    L.append(a)
    return L

期待した動作が得られました(呼び出しごとに配列がリセットされました)。

だから私は行を説明する必要があるように思えます-変数def f(a, L=[]):のスコープは何ですか?L

4

7 に答える 7

25

スコープはあなたが期待する通りです。

おそらく驚くべきことは、デフォルト値が一度だけ計算されて再利用されることです。そのため、関数を呼び出すたびに、[]に初期化された新しいリストではなく、同じリストが取得されます。

リストはにf.__defaults__(またはf.func_defaultsPython 2に)保存されます。

def f(a, L=[]):
    L.append(a)
    return L

print f(1)
print f(2)
print f(3)
print f.__defaults__
f.__defaults__ = (['foo'],) # Don't do this!
print f(4)

結果:

[1]
[1, 2]
[1, 2, 3]
([1, 2, 3],)
['foo', 4]
于 2010-02-25T15:28:08.393 に答える
7

変数のスコープはL期待どおりに動作しています。

「問題」は、で作成しているリストにあります[]。Pythonは、関数を呼び出すたびに新しいリストを作成するわけではありません。 L呼び出すたびに同じリストが割り当てられるため、関数は以前の呼び出しを「記憶」します。

つまり、実際にはこれがあなたの持っているものです:

mylist = []
def f(a, L=mylist):
    L.append(a)
    return L

Pythonチュートリアルでは、次のように説明しています。

デフォルト値は1回だけ評価されます。これは、デフォルトがリスト、ディクショナリ、またはほとんどのクラスのインスタンスなどの可変オブジェクトである場合に違いを生みます。

そして、期待される動作をコーディングするために次の方法を提案します。

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
于 2010-02-25T15:29:24.367 に答える
3

あなたが想像するよりもさらに少ない「魔法」があります。これは同等です

m = []

def f(a, L=m):
    L.append(a)
    return L

print f(1)
print f(2)
print f(3)

m一度だけ作成されます。

于 2010-02-25T15:30:47.980 に答える
2

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

def func(a=[]):
    a.append(1)
    print("A:", a)

func()
func()
func()

Pythonのインデントを使用して、何が起こっているのかを理解するのに役立てることができます。左マージンにフラッシュされるものはすべて、ファイルが実行されるときに実行されます。インデントされたものはすべてコードオブジェクトにコンパイルされ、func()が呼び出されたときに実行されます。したがって、関数が定義され、プログラムが実行されるときにデフォルトの引数が1回設定されます(defステートメントが左にフラッシュされるため)。

デフォルトの引数で何をするかは興味深い問題です。Python 3では、関数に関するほとんどの情報を2つの場所に配置します: func.__code__func.__defaults__。Python 2では、func.__code__でしfunc.func_code func.__defaults__func.func_defaults。2.6を含む新しいバージョンのpython2には、python2からpython3への移行を支援するために、両方の名前のセットがあります。よりモダンな__code__とを使用し__defaults__ます。古いPythonを使い続けている場合、概念は同じです。名前だけが違います。

デフォルト値はに格納されfunc.__defaults__、関数が呼び出されるたびに取得されます。

したがって、上記の関数を定義すると、関数の本体はコンパイルされて、の下の変数に格納され__code__、後で実行され、デフォルトの引数はに格納され__defaults__ます。関数を呼び出すと、の値が使用され__defaults__ます。これらの値が何らかの理由で変更された場合、使用できるのは変更されたバージョンのみです。

インタラクティブインタプリタでさまざまな関数を定義してみて、Pythonが関数を作成および使用する方法について理解できることを確認してください。

于 2010-02-25T16:31:39.777 に答える
1

説明は、この質問への回答で行われます。ここで要約すると:

Pythonの関数は一種のオブジェクトです。それらは一種のオブジェクトであるため、インスタンス化されるとオブジェクトのように機能します。関数は、デフォルトの引数として可変属性で定義されている場合、可変リストである静的属性を持つクラスとまったく同じです。

Lennart Regebroには良い説明があり、 RobertoLiffredoによる質問への回答は優れています。

レナートの答えを適応させるために...私がBananaBunchクラスを持っている場合:

class BananaBunch:
    bananas = []

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


bunch = BananaBunch()
>>> bunch
<__main__.BananaBunch instance at 0x011A7FA8>
>>> bunch.addBanana(1)
>>> bunch.bananas
[1]
>>> for i in range(6):
    bunch.addBanana("Banana #" + i)
>>> for i in range(6):
    bunch.addBanana("Banana #" + str(i))

>>> bunch.bananas
[1, 'Banana #0', 'Banana #1', 'Banana #2', 'Banana #3', 'Banana #4', 'Banana #5']

// And for review ... 
//If I then add something to the BananaBunch class ...
>>> BananaBunch.bananas.append("A mutated banana")

//My own bunch is suddenly corrupted. :-)
>>> bunch.bananas
[1, 'Banana #0', 'Banana #1', 'Banana #2', 'Banana #3', 'Banana #4', 'Banana #5', 'A mutated banana']

これは関数にどのように適用されますか? Pythonの関数はオブジェクトです。これは繰り返しに耐えます。Pythonの関数はオブジェクトです。

したがって、関数を作成すると、オブジェクトが作成されます。関数に変更可能なデフォルト値を指定すると、そのオブジェクトの属性に変更可能な値が入力され、その関数を呼び出すたびに同じ属性を操作します。したがって、変更可能な呼び出し(appendなど)を使用している場合は、オブジェクトにバナナを追加する場合と同じように、同じオブジェクトを変更しbunchます。

于 2010-02-25T15:40:16.207 に答える
0

ここでの「問題」は、一度L=[]だけ評価されること、つまりファイルがコンパイルされるときに評価されることです。Pythonはファイルの各行をステップスルーし、コンパイルします。デフォルトのパラメータでに到達するまでに、そのリストインスタンスを1回作成します。def

関数コード内に配置L = []すると、Pythonは関数のコードをコンパイルしますが呼び出しないため、インスタンスは「コンパイル時」に作成されません(実際にはコンパイル時はランタイムの一部と呼ばれることもあります)。したがって、関数を呼び出すたびに(コンパイル中に1回ではなく)作成が行われるため、新しいリストインスタンスを取得します。

この問題の解決策は、可変オブジェクトをデフォルトパラメータとして使用しないか、次のような固定インスタンスのみを使用することですNone

def f(a, L = None):
    if l == None:
        l = []
    L.append(a)
    return L

説明したどちらの場合も、のスコープLは関数スコープであることに注意してください。

于 2010-02-25T15:39:53.690 に答える
-1

Pythonはインタプリタ言語であることに注意する必要があります。ここで起こっていることは、関数 "f"が定義されると、リストを作成し、それを関数"f"のデフォルトパラメータ"L"に割り当てます。後でこの関数を呼び出すと、同じリストがデフォルトのパラメーターとして使用されます。つまり、「def」行のコードは、関数が定義されたときに1回だけ実行されます。これは一般的なPythonの落とし穴であり、私はその落とし穴に陥っています。

def f(a, L=[]):
    L.append(a)
    return L

print f(1)
print f(2)
print f(3)

この問題を修正するために、他の回答にイディオムの提案があります。私が提案するものは次のとおりです。

def f(a, L=None):
    L = L or []
    L.append(a)
    return L

これは、または短絡を使用して、渡された「L」を取得するか、新しいリストを作成します。

スコープの質問に対する答えは、「L」は関数「f」内にのみスコープを持っているということですが、デフォルトのパラメーターは、関数を呼び出すたびにではなく、単一のリストに1回だけ割り当てられるため、デフォルトのパラメーターのように動作します。 「L」はグローバルスコープを持っています。

于 2010-02-25T15:52:50.993 に答える