Python

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は引き継がれる模様。