これらの3つの問題を試し、記事の最後にある回答を確認してください。
ヒント:問題には共通点があるため、2番目または3番目に進むときに最初の問題をブラッシュアップすると、より簡単になります。
最初のタスク
いくつかの変数があります:
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
印刷する
l
と何が表示されますs
か?
2番目のタスク
簡単な関数を定義しましょう:
def f(x, s=set()):
s.add(x)
print(s)
電話をかけるとどうなりますか:
>>f(7)
>>f(6, {4, 5})
>>f(2)
3番目のタスク
2つの単純な関数を定義しましょう:
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
これらのコマンドを実行すると何が得られますか?
>>f_inner = f()
>>print(f_inner(2))
>>g_inner = g()
>>print(g_inner(2))
あなたの答えにはどの程度自信がありますか?あなたのケースをチェックしましょう。
最初の問題の解決策
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
a.append(5)
最初のリストが同じ変更を完全に無視
するのに、なぜ2番目のリストは最初のアイテムの変更に反応するのx+=5
ですか?
2番目の問題の解決策
しばらく様子を見てみましょう:
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
待って、最後の結果は
{2}
どうですか?
3番目の問題の解決策
結果は次のようになります。
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
なぜ
g_inner(2)
彼女は裏切らなかったの3
ですか?内部関数f()
が外部スコープを記憶しているのに、内部関数が記憶していないのはなぜg()
ですか?それらはほとんど同じです!
説明
これらの奇妙な振る舞いのすべてがPythonの可変オブジェクトと不変オブジェクトの違いに関係していると私が言ったらどうでしょうか?
リスト、セット、ディクショナリなどの可変オブジェクトは、適切に変更できます。数値や文字列値、タプルなどの不変オブジェクトは変更できません。それらの「変更」は、新しいオブジェクトの作成につながります。
最初のタスクの説明
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
それがあるので
x
不変、操作は、x+=5
元のオブジェクトを変更するが、新しいものを作成しません。ただし、リストの最初の項目は元のオブジェクトを参照しているため、その値は変わりません。
なぜなら 変更可能なオブジェクトの場合、コマンド
a.append(5)
は元のオブジェクトを(新しいオブジェクトを作成するのではなく)s
変更し、リストは変更を「確認」します。
2番目のタスクの説明
def f(x, s=set()):
s.add(x)
print(s)
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
最初の2つの結果では、すべてが明らかです。最初の値が
7
最初は空のセットに追加され、結果がわかり{7}
ます。次に、値が6
セットに追加されて{4, 5}
取得され{4, 5, 6}
ます。
そして、奇妙なことが始まります。値は
2
空のセットではなく、{7}に追加されます。どうして?オプションパラメータの初期値はs
1回だけ計算されます。最初の呼び出しで、sは空のセットとして初期化されます。また、変更可能なため、呼び出さf(7)
れた後に変更されます。 2番目の呼び出しf(6, {4, 5})
はデフォルトのパラメーターに影響を与えません。つまり、セットがそれを置き換えます。{4, 5}
つまり{4, 5}
、別の変数です。 3番目の呼び出しf(2)
は同じ変数を使用しますs
これは最初の呼び出しで使用されましたが、空のセットとして再初期化されるのではなく、以前の値から取得されます{7}
。
したがって、デフォルトの引数として可変引数を使用しないでください。この場合、関数を変更する必要があります。
def f(x, s=None):
if s is None:
s = set()
s.add(x)
print(s)
3番目のタスクの説明
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
ここでは、クロージャーを扱っています。内部関数は、定義時に外部名前空間がどのように見えたかを記憶しています。または少なくとも覚えておく必要がありますが、2番目の関数はポーカーフェースを作成し、外部の名前空間を聞いていないかのように動作します。
なぜこうなった?を実行する
l.append(x)
と、関数の定義時に作成された可変オブジェクトが変化します。しかし、変数はl
依然としてメモリ内の古いアドレスを参照しています。ただし、2番目の関数で不変変数を変更しようとするとy += x
、yが別のメモリアドレスを参照し始めます。元のyは忘れられ、UnboundLocalErrorが発生します。
結論
Pythonの可変オブジェクトと不変オブジェクトの違いは非常に重要です。この記事で説明する奇妙な動作は避けてください。特に:
- .
- - .