Pythonでこれらの3つの(一見)単純なタスクを解決できますか?

ソフトウェア開発者としての私の旅の最初から、プログラミング言語の内部を掘り下げることが本当に好きでした。これまたはその構造がどのように機能するか、これまたはそのコマンドがどのように機能するか、構文糖の内部に何があるかなど、いつも疑問に思っていました。最近、Pythonの可変オブジェクトと不変オブジェクトが常に機能するとは限らない例を含む興味深い記事を見つけました。私の意見では、重要なのは、使用される同一のセマンティクスと言語構造を維持しながら、使用されるデータのタイプに応じてコードの動作がどのように変化するかです。これは、書くときだけでなく、使うときにも考える良い例です。私は皆に翻訳に慣れるように勧めます。







これらの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}に追加されます。どうして?オプションパラメータの初期値はs1回だけ計算されます。最初の呼び出しで、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の可変オブジェクトと不変オブジェクトの違いは非常に重要です。この記事で説明する奇妙な動作は避けてください。特に:



  • .
  • - .



All Articles