Edited at

Python のリストでは=の使用注意

More than 1 year has passed since last update.

確認したバージョンはPython 3.6.1

それは.extend()を使ったとき起こった。


基本

#3つリスト作って

a = ["dog", "cat", "rabbit"]
b = list("333")#['3', '3', '3']
c = [2, "toka", 3.4, "みたいな混在もOK"]
d = [a,b]
print(d)

ここまでなら、間違いなく[['dog', 'cat', 'rabbit'], ['3', '3', '3']]が表示される。

そしてリストを追加する関数として.extend()があり、これでcを追加しようとした。


問題のコード

a = ["dog", "cat", "rabbit"]

b = list("333")#['3', '3', '3']
c = [2, "toka", 3.4, "みたいな混在もOK"]
d = [a,b]

#dと比較するためにeにdを代入して
e = d

#.exrend()でeにcを挿入
e.extend(c)

#では比較を
print("d=", d)
print("e=", e)

結果表示されたのは

d= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']

e= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']

あれ〜?

方法を変えてみよう

a = ["dog", "cat", "rabbit"]

b = list("333")#['3', '3', '3']
c = [2, "toka", 3.4, "みたいな混在もOK"]
d = [a,b]

ここまではいじらずに、処理を変えてみる

e = d

#.extend()と同じ結果が+=でもできる。
e += c

print("d=", d, "\n", "e=", e)

"""結果
d= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']
e= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']
"""

失敗。

そもそも、いじってないはずのdが変化してるのだ。

しかも、加えるのをeではなくdに変えても結果は同じ。

原因はほぼ確実に e = d という処理。

うまくいくわけ無いか。

なら

e = d + c

print("d=", d, "\n", "e=", e)

"""結果
d= [['dog', 'cat', 'rabbit'], ['3', '3', '3']]
e= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']
"""

成功。

やはり、e = d が問題のようだ。

しかし、なんでこんなことに?


原因

どうも、e = dで行われたのはeにdのリストをコピーではなく、eとdという変数のIDを共通化という処理がなされたよう。

a = ["dog", "cat", "rabbit"]

b = list("333")#['3', '3', '3']
c = [2, "toka", 3.4, "みたいな混在もOK"]
d = [a,b]
e = d

#id()で変数のidを確認。
print("d=", id(d), "\ne=", id(e))

e.extend(c)

print("d=", d)
print("e=", e)
print("d=", id(d), "\ne=", id(e))
"""
結果
d= 4738395144
e= 4738395144
d= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']
e= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']
d= 4738395144
e= 4738395144
"""

つまり、eとタグ付けされた箱にdとタグ付けされた箱と同じ物を入れたのではなく、eというタグをdとタグ付けされた箱に貼っただけと。

だから、eへの処理はdへの処理と同じ意味を持ったと。

何じゃそりゃ。


解決策

未だ初心者のため詳しい仕組みはわからない。

とりあえず成功例から考えて、何かの処理を加えて単なるコピーじゃなくしたらいけるかと、スライスを使ってコピーすることに

a = ["dog", "cat", "rabbit"]

b = list("333")#['3', '3', '3']
c = [2, "toka", 3.4, "みたいな混在もOK"]
d = [a,b]

#e = dをe = d[:]に変更
e = d[:]

e.extend(c)

print("d=", d, "\n", "e=", e)
print("d=", id(d), "\n", "e=", id(e))

"""結果
d= [['dog', 'cat', 'rabbit'], ['3', '3', '3']]
e= [['dog', 'cat', 'rabbit'], ['3', '3', '3'], 2, 'toka', 3.4, 'みたいな混在もOK']
d= 4738473480
e= 4739188744
"""

とりあえず成功。

これが何らかのバグなのか、それとも仕様なのかは不明。

こういう仕様のようです。

他の方法としては、

e = d

e = e[:]

e = list(d)

でもdとeのIDは別になった。


因みに

これは果たして、リストだけに起こる問題なのか?

と思い下のコードを実行

a = 1

b = 2
c = b
print("ID(a)=", id(a), "\nID(b)=", id(b), "\nID(c)=", id(c))

b += a
print(a,b,c)
print("ID(a)=", id(a), "\nID(b)=", id(b), "\nID(c)=", id(c))

"""結果
ID(a)= 4506339328
ID(b)= 4506339360
ID(c)= 4506339360
1 3 2
ID(a)= 4506339328
ID(b)= 4506339392
ID(c)= 4506339360
"""

こっちでは、c = bの時点ではIDはおなじだったが、b += aの処理でbのIDが変更された。

その後、a,bをリストにして試したら上の問題が発生。

少なくとも普通の計算では起こらない模様。

安心するべき・・・か?

むしろ、この単純計算だからこそこういった結果になっただけで、オブジェクト(変数)の中にオブジェクト(変数)が入る処理なら、同様の問題が起こるもよう。(set()とか、dict()でも確認)


更に、因みに

問題のコードで

e = d のままにして

e.extend(c)の処理の代わりに、


  • e.append()やってみた。


    • 同じ問題が起きた。



  • e[1]=[9]にした


    • 同じ問題が起きた。



  • e.remove(['3', '3', '3'])してみた


    • 同じ問題が起きた。



  • e = [0]にした。


    • eのID変わった。



どうやら、元の形式をベースに変更する限りIDは引き継がれる模様。