【Python】Aを変えると、Bが変わる不思議な変数とIDの話
不思議な変数のトリック
世の中には不思議なことがたくさんあるもので、プログラムも例外ではありません。
次のコードを実行してみてください。
a = [79, 58, 45, 4, 38]
b = a
b.append(32)
print(a)
# [79, 58, 45, 4, 38, 32]
これ、最初に見た時は「えっ?バグ?」って思いました。
でも実際はバグでもなんでもありません。
a
のリストをb
にコピーして、b
に要素を追加したらaにも要素が追加されているんです。
パッと見、ホラーですよね。
学習時のイメージが逆
実はこれ、一般的にプログラミングを教えている時に使う表現と実際の動きが逆だから起こる現象であり、違和感のもととなっています。
a = 10
この代入の流れを最初にを学ぶときは、「箱に10
という値を入れ、a
と名付ける」といった表現をされたと思います。
でも実際には違います。
実際はどこかに10
という値が生成され、a
という変数にはそれを見ています。
a
に入れるのではなく、a
が見に行っているのです。
では、a = b
はどうでしょう。
この場合は、a
と同じ対象を見ることになります。
決して、aの見ているオブジェクトと同じものが、bにコピーされる訳ではありません。
よって、今a
とb
は同じものを見ていることになります。
例に挙げたものは、数値の10
でした。
ではそれがリストとなると、どうでしょう。
次のコードを想定してみます。
a = [79, 58, 45, 4, 38]
b = a
このようになりますね。
リストのオブジェクトが生成されて、代入によりa
がそれを見ます。
a = b
により、b
も同じオブジェクトを見ます。
つまり、今a
とb
は同じものを見ています。
ここで、.append()
が起こるとどうなるでしょう。
b.append(32)
b
の見ている対象のオブジェクトに、32
が追加されました。
でもそれは、aが見ているオブジェクトでもあります。
よって、print(a)
をしても、32
が追加されているという結果になります。
あ~、スッキリ!
直接定義したものは、新規生成
あなたがコードのなかで直接定義したものは、すべて新規生成されるものになります。
10
も、'Unico'
も、['Your', 'list']
も、すべてそうです。
なので、同じ値を入れていたとしても、直接定義されていればそれは別のものを見ています。
a = 10
b = 10
気を付けるべき点は、変数間で中身を移動したい場合です。
クラスを作ると、self.value = value
などよくやりますね。
こういったときには注意して設計しましょう。
同 じ も の を 見 て い ま す 。
# テンプレートとして利用したい
params = {
'name': None,
'level': 1,
'skill': []
}
# コピーしたつもり
me = params
# パラメータを設定
me['name'] = 'Unico'
me['level'] = 20
me['skill'].append('水の魔法')
# テンプレート(params)が変わってしまう
print(params)
# {
# 'name': 'Unico',
# 'level': 20,
# 'skill': ['水の魔法']
# }
コピーしたつもりで=
を使ってしまうと、とんでもないバグになってしまいます。
リストをコピーして別々で持たせたいんだけど!?
上記のように、テンプレートとして利用したいものがあって、どうしてもコピーしたい場合には、いくつか手段があります。
その中の一つは、deepcopy
を利用することです。
deepcopy
は同じ中身の値を持った、新しいオブジェクトにして生成してくれます。
使い方は、超かんたん。
import
して、関数にコピーしたい変数を渡すだけ。
同値の新しい生まれたてのオブジェクトにして、戻してくれます。
from copy import deepcopy
# テンプレート
params = {
'name': None,
'level': 1,
'skill': []
}
# コピーしたつもり
me = deepcopy(params)
# パラメータを設定
me['name'] = 'Unico'
me['level'] = 20
me['skill'].append('水の魔法')
# テンプレート(params)は変わっていない
print(params)
# {
# 'name': None,
# 'level': 1,
# 'skill': []
# }
# deepcopyした方(me)は変わっている
print(me)
# {
# 'name': 'Unico',
# 'level': 20,
# 'skill': ['水の魔法']
# }
もっと知りたい方は・・・
変数のID、ミュータブル、イミュータブルな変数について調べてみよう!
それではよいPythonライフを。
Discussion