「Pythonの引数の引き渡し」の版間の差分

提供: tknotebook
移動: 案内検索
(引数は値渡し(Call By Value))
(整数や文字列は immutable)
63行: 63行:
 
一度作成されると変更できないオブジェクトを immutable と言います。python では基本的な数値型(int, float), str型、bool型はimuutableです。整数や文字列が変更できないというのは奇異に思われるかもしれませんが Javaの String型も同じです。
 
一度作成されると変更できないオブジェクトを immutable と言います。python では基本的な数値型(int, float), str型、bool型はimuutableです。整数や文字列が変更できないというのは奇異に思われるかもしれませんが Javaの String型も同じです。
  
imuutableなオブジェクトを指す変数を書き換えるのは、新しいオブジェクトを作ってその参照を変数に代入します。変数の保持する参照は変更可能なので、それを変えれば値を実質的に変更できるわけです。
+
immutableなオブジェクトを指す変数を書き換えるのは、新しいオブジェクトを作ってその参照を変数に代入します。変数の保持する参照は変更可能なので、それを変えれば値を実質的に変更できるわけです。
  
 
imuutableなオブジェクトを関数の引数に渡すと 実質的に値渡しになります。
 
imuutableなオブジェクトを関数の引数に渡すと 実質的に値渡しになります。

2018年9月12日 (水) 05:54時点における版

メインページ>コンピュータの部屋#Python>Python Tips


pythonは大変わかりやすい言語ですが、ひとつわかりにくい基本的な事項があります。 関数への引数の引き渡し方です。

引数の引き渡し方といえば

  1. 値渡し(Call By Value)
  2. 参照渡し(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なオブジェクトを指す変数を書き換えるのは、新しいオブジェクトを作ってその参照を変数に代入します。変数の保持する参照は変更可能なので、それを変えれば値を実質的に変更できるわけです。

imuutableなオブジェクトを関数の引数に渡すと 実質的に値渡しになります。

def fun(a):
  a = "foo"

b="bar"
fun(b)
print(b)

で、bは文字列オブジェクト"bar" の参照を保持しますが、仮引数a にわたるのは、bではなくbの保持する参照です。 aに "foo" 文字列オブジェクトの参照を代入しても、bの参照している文字列オブジェクトはもちろん変更されません。 aの保持する参照を書き換えただけなので当然です。

def fun(a):
  a = 2

b=1
fun(b)
print(b)

も 2 ではなく 1 を表示します。b には 整数オブジェクト 1 への参照が代入されており、それの参照が引数として fun関数のローカル変数 a に渡されますが、ローカル変数 a が指す参照が 整数オブジェクト 2 になっても グローバル変数 b の指すオブジェクトには全く影響しません。

つまり、int や float のような immutable なオブジェクトは絶対に変更できないので、変数の指す「値」を変えるということは新たなオブジェクトを作成してその参照を代入するということです。引数への代入は、引数を通じて関数から呼び出し元へ伝わることはないので、引数は値渡しで渡されると考えてよいわけです。

参照渡しと 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 なオブジェクトの参照を仮引数で受け、仮引数の持つ参照を上書きすることなく、仮引数の指すオブジェクトを変更すると、呼び出し元が引数として与えたオブジェクトを変更できます。これは他の言語の「参照渡し」と同じような動きといえます。

まとめ

まとめると

  1. immutableなオブジェクトの引数は実質的に値渡しです。
  2. 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