Pythonの変数と代入について
Pythonの変数、ふだん何気なく使っていますが、やっていることは実は結構複雑です。主にC/C++と対比しつつ簡単にまとめてみます。
実際のところ、よく知らなくてもあまり影響がない場合が殆どです。が、複雑な操作を行ったり何か変数の挙動が不審な場合などは、思い出してみるのもよさそうです。
変数は参照である
一言でいえば表題の通り。 Pythonでの変数への代入とは、変数の参照するメモリ(インスタンス)を切り替えることであり、メモリの内容を書き換えることではない。これが多くの言語(C/C++など)と大きく違うところです。
変数の代入(1)
例えばこんなコード。
# Python3
a = 0
a = 1
print(a) # 1を出力
C++で書き換えると、
// C++
int a = 0;
a = 1;
cout << a << endl; // 1を出力
なのか?? (#include\とかint main(void){...}とかは全部省略。)
実は違うのです。
C++の方ではa
で表されるメモリ領域を一つ確保し、そこにまず0
をセット、次に1
を、同じメモリ領域を書き換えてセットしています。
Pythonの方はそうではありません。まず0
の値を持つメモリ領域を確保し、変数a
がそこを指すようにします。次に1
の値を持つ別のメモリ領域を確保し、変数a
がそこを指すように切り替えます。
C++で無理矢理それっぽいものを書くと、
// C++
int *a = new int(0);
a = new int(1);
cout << *a << endl; // 1を出力
のようになります。
(C++に参照定数はあるが参照変数がないため、ポインタで書いた。)
実はこのC++コード、2行目で確保したint(0)
メモリを3行目のint(1)
代入時に放棄していて、メモリリークが起こっています。C++では、これを処理(メモリ解放)するためのコードを本当は書き加えないといけません。
しかし、Pythonではそれをガベージコレクタという仕組みが勝手に代行してくれます。逆にこれがないと、Pythonではメモリリークが頻繁に起こって大変なことになります。
変数の代入(2)
問題です。次のコードは何を出力するでしょうか。
# Python3
a = 0
b = a
a = 1
print(b) # 何が出る?
答えは「0
」です。
C++で書いた似たようなコード、
// C++
int a = 0;
int b = a;
a = 1;
cout << b << endl; // 出力は「0」
も結果は同じなのですが、内部動作は全く異なります。Pythonでの変数代入は参照の切り替えであることがやはりポイントです。
一方で、過程は違えどPythonもC++も結果は変わりません。気にしなくても大抵はうまくいく、というのも大事なところです(笑)。
関数引数はすべて参照渡し
「変数が全て参照」であることから、関数引数もまた参照渡しとなります。C/C++のデフォルトである値渡しと異なり、メモリのコピーなどは行われません。
では再び問題。次のコードは何を出力するでしょう?
# Python3
def func(x):
x = 1
a = 0
func(a)
print(a)
# ここまで。このprint()は何を出力するか?0?1?
「参照渡し」を知っている人ほど、「1」と答えたくなりそうですが、、
答えは「0」です。何故か?
func(a)
を呼び出した時点では、a
とx
の指すメモリは同じ0
ですが、x = 1
でx
の指すメモリは別に確保された1
に切り替わります。a
および、a
の指す0
には何の変化も無いのです。このあたりは前項の問題とほとんど同じです。
「変数への代入」ではない場合
例えばこんなとき。
# Python3
a = [0, 1]
b = a
a[0] = 2
print(b) # 出力は[2,1]
3行目でb
とa
は同じリストを参照するようになります。4行目では、そのリスト(これも参照の列みたいなもの)の最初の成分を、0
でなく2
のメモリを参照するように切り替えます。全体として見ればa
とb
が同じリストを参照していることに変わりはないので、この変更はb
にも反映されています。
これは、前項の関数引数で若干の問題を引き起こします。
次のコード、前項で見たものにそっくりですが、、、
# Python3
def func2(x):
x[0] = 1
a = [0]
func2(a)
print(a[0])
前項とは違い、この出力は「1
」です。
関数が呼び出されるとa
とx
の指すリストは一貫して同じで、その一部が書き換えられるからです。これはリストでなくnumpy配列の場合もほぼ同じです。
まとめ
以上見てきたように、Pythonの変数は全て参照、関数引数は全て参照渡しです。これらが組み合わさると、結果的に全てを値渡しにした場合(C/C++)と殆ど違いが見えなくなり、あまり意識することなくプログラムを書けるようになっています。
しかし、リストや配列(や、もっと複雑なクラスオブジェクトなど)のように部分的に書き換え可能なものを扱う場合などには、この違いはかなり重要になってきます。怪しいと思ったら、各変数が何を参照しているか、互いに同じか違うかなどを、その都度考えてみてください。
ひとまず今回はここまで。おしまい。 ※画像は公式のものです。
- 前の記事 : Common Workflow Language入門
- 次の記事 : 【論文まとめ】医師国家試験問題自動生成AI
- 関連記事 :