Pythonの参照渡しと(値渡し)

まずはこちらをご覧ください。


Pythonのサンプルコード書いてみました。

class Test:

    def __init__(self) -> None:
        self.list_ = [1,2,3,4]
        self.num = 100
    
    def get_num(self):
        return self.num
    
    def get_list(self):
        return self.list_

test = Test()

num = test.get_num()
num = 200

# test.numは...?100 or 200?

list_ = test.get_list()
list_.append(700)

# test.list_に700はいる or いない?


...numlist_test.numtest.list_をprintした時、何が出力されると思いますか?

ちなみに私は、

- num = 200、test.num = 100
- list_ = [1,2,3,4,700]、test.list_ = [1,2,3,4]

だと思ってました 👀

はい、これは、間違いですね ☝️


正解は...


# 出力部分の実装
print(f'test.num = {test.num}' + f'\tアドレス:{id(test.num)}')
print(f'num = {num}' + f'\tアドレス:{id(num)}')
print('==================================')
print(f'test.list_ = {test.list_}' + f'\tアドレス:{id(test.list_)}')
print(f'list_ = {list_}' + f'\tアドレス:{id(list_)}')

# 結果

test.num = 100  アドレス:4332965200
num = 200       アドレス:4332968400
==================================
test.list_ = [1, 2, 3, 4, 700]  アドレス:4334289664
list_ = [1, 2, 3, 4, 700]       アドレス:4334289664


id で各変数のアドレス(格納先)も出力させてみました。

こうすると、なんで numlist_ で挙動が違うのかが、わかるかと思います。

リストの場合は、どうも参照渡しになっているのです 👀 !

(調べて分かったのですが、 num も実は参照渡し...後述)

※ アドレスは実行環境によって変わります。

色々調べてみた結果...


Pythonには、値渡しが存在しない 👀 !?
  • さっきは返り値 num に代入していたので気づかなかったのですが、代入しないでアドレスを見ると...一緒でした 👀
  • タイトルの値渡しを( )にしている理由です

      test = Test()
    
      num = test.get_num()
    
      # 結果
      test.num = 100  アドレス:4395519312
      num = 100       アドレス:4395519312
    
    • 値が代入される時に、新しいオブジェクト(格納先)が作られて、そこに代入値が入れられるらしい
      test.num = 100  アドレス:4332965200
      num = 100       アドレス:4332965200
    
      num = 200 の後↓
    
      test.num = 100  アドレス:4332965200
      num = 200       アドレス:4332968400 # アドレス違う
    


型によって挙動が変わるらしい
  • イミュータブルな型 → 代入時に新しいオブジェクトが生成される(結果的に値渡しのような挙動になる)
  • ミュータブルな型 → 参照渡しになるので、元の値も変わってしまう
  • Pythonではどの型がイミュータブルなのかは一覧表↓

Pythonの組み込みデータ型の分類表(ミュータブル等) - ガンマソフト株式会社


Python 3.10ですが、公式ドキュメントにも、ちらっとイミュータブルについて説明が...

3. Data model - Python 3.10.4 documentation

オブジェクトによっては 値 を変更することが可能です。値を変更できるオブジェクトのことを mutable と呼びます。
生成後に値を変更できないオブジェクトのことを immutable と呼びます。

(mutable なオブジェクトへの参照を格納している immutableなコンテナオブジェクトの値は、
その格納しているオブジェクトの値が変化した時に変化しますが、
コンテナがどのオブジェクトを格納しているのかが変化しないのであれば immutable だと考えることができます。
したがって、immutable かどうかは値が変更可能かどうかと完全に一致するわけではありません) 

オブジェクトが mutable かどうかはその型によって決まります。
例えば、数値型、文字列型とタプル型のインスタンスは immutable で、dict や list は mutable です。

※ コンテナ = 任意の型のデータを複数格納できるデータ型(list, tuple, dict, set)

参考

こちらの記事を主に参考にしました!

参照についてもう少し詳しく ~PythonとJavaを例に~ - Qiita

感想

  • Pythonには、値渡しが存在しないというのは知らなかったし、もはや、リストさえも値渡しだと思っていたので、色々間違って理解していたようです。
  • これからリストを返り値で返すときは、参照するだけなら良いですが、変えてしまう可能性がある場合(あまり良くはない)は、新しいリストオブジェクトを作成して返すと事故らなそうですね。