どうも!リョクちゃです。
今回は、以前にVB.Netでも触れた値渡しと参照渡しについて
Pythonではどうなっているのかを考えていきたいと思います。
値渡しと参照渡しについて考える前に、関数について振り返っていきます。
<今回のテーマ>
- 関数で定義する引数
- デフォルト引数とキーワード引数
- 値渡しと参照渡し
このようなテーマで振り返りながら新しいことを紹介していきます。
ちなみに前回はこちら
目次
関数で定義する引数
引数(ひきすう)とは、関数やメソッドに対して値を渡すときに
使われるパラメータのことを指します。
変数に分類され、特別な変数として位置づけられます。
引数には2種類あります。(※関数編では触れていませんでした。)
- 仮引数
- 実引数
仮引数(かりひきすう)
関数を定義するときに変数で指定する引数のことをいいます。
たとえば、
1 2 |
def sum(x, y): return x + y |
sum()内のx,yが仮引数となります。
実引数(じつひきすう)
関数を実行するときに渡される引数のことを言います。
たとえば、
1 2 3 4 |
def sum(x, y): return x + y print(sum(10, 20)) |
関数を呼び出す際の10や20が実引数になります。
デフォルト引数とキーワード引数
デフォルト引数(でふぉるとひきすう)
関数の引数にあらかじめ初期値が与えられている引数のことをいいます。
たとえば、
1 2 3 4 5 |
def hello(msg="こんにちは", target="World"): print(msg + " " + target) hello() |
キーワード引数(きーわーどひきすう)
関数で引数として定義された変数のことをいいます。
VB.Netでいう、引数を割り当てる際の
Optional ByVal <変数名> as <型名> = <初期値>のイメージになります。
たとえば、
1 2 3 4 5 |
def hello(msg="こんにちは", target="World"): print(msg + " " + target) hello() |
デフォルト引数とキーワード引数を組み合わせた例を下にコードで示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def hello(msg="こんにちは", target="World"): print(msg + " " + target) # デフォルト引数のみ hello() hello("はじめまして", "赤ちゃん") hello("good morning") # 出力 # こんにちは World # はじめまして 赤ちゃん # good morning World # デフォルト引数+キーワード引数 hello(msg="おはようございます", target="みなさん") hello(target="Tom", msg="おはようございます") hello(target="TANAKA") # 出力 # おはようございます みなさん # おはようございます Tom # こんにちは TANAKA |
また、キーワード引数を指定して値を渡すことを“キーワード渡し”といいます。
下の図をご参照ください。
値渡し(Call by value)と参照渡し(Call by reference)
これらは結論から言うと、関数に引数を渡す方法になります。
Pythonにおける引数渡しは、基本的には参照渡しになります。
VB.Netでいうところの、
- 値渡し → ByVal
- 参照渡し → ByRef
にそれぞれあたります。
以降で、値渡しと参照渡しについてそれそれ説明していきます。
値渡し(Call by value)
関数の仮引数に実引数をコピーして渡す方法になります。
たとえば、
1 2 3 4 5 6 7 8 9 |
def test(x): x += 1 a = 10 test(a) print(a) # 出力 # 10 |
これは、
ここで、なぜ変数aの値は変更されていないのか……?
といった疑問があるかと思います。
これについては、後ほど説明します。
参照渡し(Call by reference)
関数に実引数のメモリ上のアドレスを渡す方法になります。
ん?……ここで聞きなれないワードが出てきました。
メモリ……?上のアドレス……?
メモリ上のアドレス……とは?(変数を作成するまで)
Pythonでは変数、関数、クラスや型などプログラムのすべてのデータは
オブジェクトとして表現されます。
これらオブジェクトの実態はメモリ上に確保された領域(メモリ空間)にあります。
たとえば、下に示すような変数を作成します。
1 2 3 |
a = 2 # 変数aを新規作成 b = a # 変数bを変数aの別名として設定 c = 5 # 変数cを新規作成 |
これを模式的にあらわすと、
変数aやb、cはそれぞれ数字にくっついている札になります。
メモリ空間内で□の部屋に割り当てられた数字がその数字のアドレス(番地)になります。
アドレスは住所や居住を示します。(address)
たとえば、変数aやbという札は、作成した時点で2に結びつけられています。
Pythonでは変数を作成する際に、上記のような処理が行われています。
メモリアドレスって?
先ほどは変数が作成されるまでのメモリ空間上でのお話をしました。
ここで作成される際に割り当てられるメモリアドレスについて説明します。
変数が作成されるとメモリ空間上のアドレスに割り当てられます。
その際に割り当てられる番号をメモリアドレス(or id)が割り振られます。
イメージで表すと、
メモリアドレスを知るには……?
Pythonではオブジェクトに割り振られた番号を、id()関数を使うことで、
そのオブジェクトがどこに格納されているかを知ることができます。
以下コードを示す。
1 2 3 4 5 6 7 |
a = 2 b = a c = 5 print(id(a)) print(id(b)) print(id(c)) |
これを実行すると、
1 2 3 4 |
# 出力 # 変数a = 1536406624 # 変数b = 1536406624 # 変数c = 1536406720 |
変数aと変数bが同じメモリアドレスであることがわかります。
※メモリアドレスは必ず実行例と同じになるわけではないので問題ありません。
ここで、
1 2 3 4 5 6 7 |
a = 2 b = a print(id(a)) print(id(b)) b = 3 print(id(b)) |
変数bを途中で変えた場合どうなるでしょうか?
1 2 3 4 |
# 出力 # 変数a = 1536406624 # 変数b = 1536406624 # 変数b = 1536406656 |
となります。
これは、b=aでは、変数bも変数aも同じメモリアドレスを参照しています。
しかし、変数bが新たに値を持つとき、
この場合は新たに3という値を持つオブジェクトの場所を変数bが参照するようになります。
そのため、変数bと変数aのIDが別のモノになります。
まとめ
値渡しでは、仮引数を変更したときに実引数は変更されませんでした。
一方で参照渡しでは、仮引数を変更すると実引数が変更されました。
これらはなぜ違うのでしょうか?
仮引数を変更したときに実引数も変更されるかどうかは、
渡されたオブジェクトの型に依存します。
オブジェクトの型が変更可能なミュータブルな型では、元の値も変更されます。
一方で変更不可なイミュータブルな型では元の値は変更されません。
以下それぞれの主な型を示します。
変更不能な型 | 変更可能な型 |
---|---|
int | リスト(list) |
float | 辞書(dictionary) |
文字列(String) | バイト配列 |
タプル(tuple) | 集合(Set) |
byte | - |
complex | - |
Pythonでは基本的にすべて参照渡しで渡されます。
ただし変更不能な型を持つオブジェクトが渡された場合は、
値渡しのようにふるまわれます。
※厳密には値渡しではありません。
・こちらの書籍を参考にPythonの理解を深めました。