Pythonの引数の引き渡し
メインページ>コンピュータの部屋#Python>Python Tips
pythonは大変わかりやすい言語ですが、ひとつわかりにくい基本的な事項があります。
関数への引数の引き渡し方です。
引数の引き渡し方といえば
- 値渡し(Call By Value)
- 参照渡し(Call By Reference)
の2種類が定番ですが、pythonでは見かけ上全く同じ書き方でこの2種類が入り混じるように見えます。
例えば
def fun(a): a = "foo" b="bar" fun(b) print(b)
を実行すると
bar
と表示されて 値渡しは明らかです。
しかし
def fun(a): a.append(3) b = [1, 2] fun(b) print(b)
は [1, 2, 3] を出力します。
このあたりの違いはどうなっているのでしょうか?
ここではこの点について解説します。
目次
全てのデータはオブジェクト
pythonでは全てのデータはオブジェクトです。変数はオブジェクトの「参照」を保持します。
int や float や str もオブジェクトで変数はその値を直接保持していないのです。
効率化のため実装は異なるかもしれませんが、このように考えておいて問題ありません。
引数は値渡し(Call By Value)
pythonでは全ての引数は「値」で渡されます。Call By Value です。但し渡されるのはオブジェクトの参照です。
ならば実質的に 参照渡し(Call By Reference)になるのではと短絡的に考える人もいるかもしれませんが、 よく考えてみましょう。一般に 参照渡し(Call By Reference)と呼ばれているものの「参照」は実引数の参照です。 オブジェクトの参照ではありません。python が値渡しで渡す参照はオブジェクトの参照です。
整数や文字列は immutable
一度作成されると変更できないオブジェクトを immutable と言います。python では基本的な数値型(int, float、等), str型、bool型はimuutableです。整数や文字列が変更できないというのは奇異に思われるかもしれませんが Javaの String型も同じです。
immutableなオブジェクトを指す変数を書き換えるには、新しいオブジェクトを作ってその参照を変数に代入します。変数の保持する参照は変更可能なので、それを変えればimmutableなオブジェクトを指す変数の値を実質的に変更できるわけです。
immutableなオブジェクトを関数の引数に渡すと 実質的に普通の言語でいうところの値渡しになります。
def fun(a): a = "foo" b="bar" fun(b) print(b)
で、bは文字列オブジェクト"bar" の参照を保持しますが、仮引数a にわたるのは、bの保持する参照のコピーです。 aに "foo" 文字列オブジェクトの参照を代入しても、bの保持している参照はもちろん変わりません。
def fun(a): a = 2 b=1 fun(b) print(b)
も 2 ではなく 1 を表示します。b には 整数オブジェクト 1 への参照が代入されており、そのコピーが fun関数のローカル変数 a に渡されますが、ローカル変数 a が指す参照が 整数オブジェクト 2 になっても グローバル変数 b の指すオブジェクトには全く影響しません。
つまり、int や float のような immutable なオブジェクトは絶対に変更できないので、変数の指す「値」を変えるということは新たなオブジェクトを作成してその参照を代入するということです。python の仮引数は実引数とは別物で、実引数から仮引数へオブジェクトの参照が値渡しでコピーされるだけです。仮引数への代入は、引数を通じて関数から呼び出し元へ伝わることはないので、引数は値渡しで渡されると考えてよいわけです。
所謂 参照渡しと mutable
pythonには内容を変更可能なオブジェクトもあります。もっともよく使うのは list型ではないでしょうか?
def fun(a): a.append(3) b = [1, 2] fun(b) print(b)
上の場合 [1,2,3]と表示されますが、関数 fun での引数 a への操作が、関数外へ影響するのは、ローカル変数に別の新たなオブジェクトの参照を代入していないからです。
つまり、グローバル変数 b と ローカル変数 a は同じオブジェクトの参照を持っているので、ローカル変数a のもつ参照を使って、オブジェクトの「中身」を変更すると、それはグローバル変数 b からも見ることができます。同じオブジェクトを指しているので当然でしょう。
このように mutable なオブジェクトの参照を仮引数で受け、仮引数の持つ参照を上書きすることなく、仮引数の指すオブジェクトを変更すると、呼び出し元が引数として与えたオブジェクトを変更できます。これは他の言語の「参照渡し」と少しだけ似ていますが、別物と考えた方がよいでしょう。参照を値渡しすることと、引数の参照を渡すことは明確に異なる引数の渡し方です。
まとめ
まとめると
- pythonのデータは全てオブジェクトであり、引数の引き渡しはオブジェクトへの参照の値渡し(コピー)です。
- immutableなオブジェクトの引数は実質的に他の言語でいうところの値渡しです。
- mutable なオブジェクトの引数は、仮引数を別のオブジェクトの参照で上書きしない限り、仮引数の持つオブジェクト参照を通じてオブジェクトに対して行った変更を、呼び出し側に伝えることができます。
補足1
pythonのimmutableなオブジェクトが内部に mutable なオブジェクトを抱えることが有ります。例えば tuple は immutable ですが list は mutable なので listを要素として持つ tuple は immutable ですが部分的に mutable になります。例えば、
a=(1, 2, [3, 4]) の a[2].append(5) は実行できます。
つまり immutable なオブジェクトを引数で渡しても、その中の mutable なオブジェクトを変更することで実質的かつ部分的に呼び出し元のオブジェクトの変更を実現できます。ご注意ください。
補足2
基本的な immutableな型
int, float, str, tuple, bool, range, type(None)
その他の immutableな型
bytes, complex, frozenset, slice, type, type(Ellipsis), type(NotImplemented), types.FunctionType, types.BuiltinFunctionType, weakref.ref