Subscribed unsubscribe Subscribe Subscribe

Python が値渡しだかなんだか、参照渡しなんだかが理解できない。

Django


f:id:domodomodomo:20170218102506j:plain
図. 値渡しのイメージ

値渡し (call by value) である。

関数を呼び出す際の実際の引数 (実引数) は、関数が呼び出されるときに関数のローカルなシンボルテーブル内に取り込まれます。そうすることで、引数は 値渡し (call by value) で関数に渡されることになります

4. その他の制御フローツール — Python 3.6.0 ドキュメント

 

ここで渡されている値、オブジェクトへの参照、というのは具体的には id という値だと思っています。

(ここでの 値 (value) とは常にオブジェクトへの参照(reference) をいい、オブジェクトの値そのものではありません)

4. その他の制御フローツール — Python 3.6.0 ドキュメント

 

id はオブジェクトに与えられる識別用の番号です。

id(object)
オブジェクトの “識別値” を返します。この値は整数で、このオブジェクトの有効期間中は一意かつ定数であることが保証されています。有効期間が重ならない 2 つのオブジェクトは同じ id() 値を持つかもしれません。
CPython 実装の詳細: This is the address of the object in memory.

2. 組み込み関数 — Python 3.6.0 ドキュメント

 

でも、なんかもう一度よく読んでみると後ろに オブジェクトへの参照渡し(call by object reference) って書いてある... 参照渡しって書いてある...

関数を呼び出す際の実際の引数 (実引数) は、関数が呼び出されるときに関数のローカルなシンボルテーブル内に取り込まれます。そうすることで、引数は 値渡し (call by value) で関数に渡されることになります(ここでの 値 (value) とは常にオブジェクトへの参照(reference) をいい、オブジェクトの値そのものではありません) [1]。
...
[1] 実際には、オブジェクトへの参照渡し(call by object reference) と書けばよいのかもしれません。というのは、変更可能なオブジェクトが渡されると、関数の呼び出し側は、呼び出された側の関数がオブジェクトに (リストに値が挿入されるといった) 何らかの変更に出くわすことになるからです。

4. その他の制御フローツール — Python 3.6.0 ドキュメント

それでも 参照渡し (call by reference) ではない。

id ってアドレス、参照みたいなものなんやろ。だから参照を渡しているから参照渡しなのかと思いきや。値渡しか、参照渡しは、引数をどうやって渡すかによって決まります。新たに id という値のコピーを作って渡しているので、値渡しということでしょうか。

値渡し (call by value) :
メソッドの引数に、新たに値のコピーを作って渡す方法のこと。

参照渡し (call by reference) :
メソッドの引数に、変数そのものの参照を渡す方法のこと。

対象を「どうやって渡すか」を表しているのが「値渡し」や「参照渡し」です。何が渡されるかではないのです。つまり「値」を渡すから「値渡し」で「参照」を渡すから「参照渡し」というわけでは断じてないのです。

もう参照渡しとは言わせない - Qiita

 

オブジェクトへの参照渡し(call by object reference)

じゃあ、この値渡し (call by value) に分類される オブジェクトへの参照渡し(call by object reference) というものが、どういう動作をしていているのかを見ていきたいと思います。

問題

実行結果 1, 2, 3, 4, 5 には何が出力されるでしょうか?

>>> class Person():
...   def __init__(self, name):
...     self.name = name
... 
>>> 
>>> person = Person('yaruo')
>>>
>>> id(person)
4509000536
>>>
>>> person.name
'yaruo'
>>>
>>>
>>> # 1. 名前を変える関数
>>> def change_name(person, new_name):
...   print(id)
...   person.name = new_name
... 
>>>
>>> change_name(person, 'yarumi')
実行結果1
>>> 
>>> person.name
実行結果2
>>>
>>>
>>> # 2. オブジェクト本体を変えたい関数
>>> def change_person(target_person):
...   print(id(target_person))
...
...   old_person = target_person
...   new_person = Person('yaranaio')
...   target_person = new_person
...
...   print(id(target_person))
... 
>>> 
>>> change_person(person)
実行結果3
3423489201
>>> 
>>> id(person)
実行結果4
>>>
>>> person.name
実行結果5

答え

# 実行結果1
4509000536

# 実行結果2
yarumi

# 実行結果3
4509000536

# 実行結果4
4509000536

# 実行結果5
yarumi

実行結果2 から change_name 関数はオブジェクトの値 name を変えることができました。しかし、実行結果5 から change_person 関数はオブジェクトを替えることができないことがわかりました。なぜ換えられなかったのでしょうか?

解説

オブジェクトは変わらないけど、オブジェクトの値は変わるってどういうこと?

1. change_name がやっていること...
id 4509000536 であるオブジェクトの name という属性に 'yarumi' という文字列への id を渡しています。属性に格納していた値 id を別の id に書き換えています。

2. change_person がやっていること...
target_person という変数に id 3423489201 を渡しています。そして変数 target_person は局所変数なので、関数の呼び出し元には残りません。局所変数の中身を取り替えただけです。

変数は箱だと思うと理解しやすいかもしれません。Pthon は値渡しなので箱の中身 id だけを渡しています。もし Python が参照渡し(call by reference) なら、この箱ごと関数に引数として渡されます。そして、もし参照渡しだったとして、箱ごと渡していたら change_person 関数を使うと引数 target_person に代入された変数 person は、関数から抜けると中身が変わって Person('yaranaio') の id が帰って来る動作をします。
f:id:domodomodomo:20170218102506j:plain

>>> class Person():
...   def __init__(self, name):
...     self.name = name
... 
>>> 
>>> person = Person('yaruo')
>>>
>>> id(person)
4509000536
>>>
>>> person.name
'yaruo'
>>>
>>>
>>>
>>> # 名前を変える関数
>>> def change_name(person, new_name):
...   print(id)
...   person.name = new_name
... 
>>>
>>>
>>> # id 450900536 のオブジェクト
>>> # つまり person の name が書き換えられた...
>>> change_name(person, 'yarumi')
450900536
>>> 
>>> person.name
'yarumi'
>>>
>>>
>>>
>>> # オブジェクト本体を変えたい。
>>> def change_person(target_person):
...   print(id(target_person))
...
...   old_person = target_person
...   new_person = Person('yaranaio')
...   target_person = new_person
...
...   print(id(target_person))
... 
>>> 
>>> # 引数 target_person に格納されていた id が
>>> # 4509000536 から 3423489201 に
>>> # 変わったことがわかります。 
>>> change_person(person)
4509000536
3423489201
>>> 
>>> # しかし引数 target_person は変数 person ではないので
>>> # person の id は変わらない...
>>> id(person)
4509000536
>>>
>>> # 当然 person の値 name も変化無しとなります。
>>> person.name
'yarumi'









Remove all ads