📝
値渡しと参照渡しの理解と、Python での実際の挙動
この記事では、プログラミングにおける「値渡し(call by value)」と「参照渡し(call by reference)」の概念を整理するとともに、Python における実際の挙動がどのようになっているかを解説します。
「値渡し」と「参照渡し」の一般的な定義
値渡し (call by value)
関数に 引数のコピー を渡す方法を指します。関数内で引数の値を変更しても、呼び出し元の変数には影響がありません。
参照渡し (call by reference)
関数に 変数そのもの(変数が参照しているアドレス) を渡す方法を指します。関数内で変数に代入などを行うと、呼び出し元の変数にも変更が反映されます。
たとえば、C++ で int&
を使ったり、C# で ref
を使ったりする場合が典型的な「参照渡し」です。これらの言語では、関数内で再代入した結果が呼び出し元の変数を直接上書きする ということが起こります。
Python は「参照渡し」ではない?
よく「Python は参照渡しだ」「いや、Python に参照渡しは無い」という議論を目にします。これは、Python が「オブジェクトへの参照を値として渡す(pass-by-object-reference あるいは pass-by-assignment)」という少し特殊な仕組みを取っているためです。
- 関数に渡されるのは「オブジェクトへの参照」であって、呼び出し元の 変数そのもの ではない。
- 関数の中でローカル変数を別のオブジェクトに再束縛しても、呼び出し元の変数の参照先は変わらない。
- ただし、ミュータブル(可変)なオブジェクトを受け取った場合、そのオブジェクト自体を変更すれば呼び出し元にも変化が見える。
例1:イミュータブルオブジェクト (int) の場合
def increment_value(value):
# 仮引数 value を別の数値に上書きしているだけ
value = value + 1
print("関数内 value:", value)
x = 10
increment_value(x)
print("関数外 x:", x)
結果
関数内 value: 11
関数外 x: 10
-
value
はあくまで「呼び出し元の x が指している整数オブジェクト」を「値として受け取っているだけ」で、関数内ではvalue
に 1 を加えた新しい整数オブジェクトをvalue
に再束縛しているだけです。 - この再束縛は呼び出し元の
x
には影響しません。
つまり「参照渡し」というよりは、「オブジェクトへの参照を値として渡し、その後、ローカル変数として再束縛している」という挙動に近いと言えます。
例2:ミュータブルオブジェクト (list) の場合
def increment_list_element(arr, index):
arr[index] = arr[index] + 1
print("関数内 arr:", arr)
numbers = [1, 2, 3]
increment_list_element(numbers, 1)
print("関数外 numbers:", numbers)
結果
関数内 arr: [1, 3, 3]
関数外 numbers: [1, 3, 3]
-
numbers
はミュータブル(可変)なリストです。 - 関数にリストの参照が「値として」渡され、関数内でリスト自体を更新したため、呼び出し元の
numbers
にも反映されます。 - ただし、
arr
にまったく別のリストを再代入しても、呼び出し元のnumbers
は変わりません。「同じオブジェクトを参照しているので、そのオブジェクトの内容変更が反映される」という点は、いわゆる「参照渡し」と似ていますが、変数の束縛を共有しているわけではない点に注意が必要です。
まとめ:Python で意識すべきポイント
-
同じオブジェクトを操作すれば変化が見える一方で、変数のバインド(束縛)自体は共有されない
- いわゆる “値渡し” や “参照渡し” というよりは、「オブジェクトへの参照を値として渡す」というのが最も近い表現です。
- 関数内で仮引数を新しいオブジェクトに代入しても、呼び出し元の変数が参照しているオブジェクトは変わりません。
-
「値を変更したいのか、オブジェクトそのものを変更したいのか」を明確にする
- イミュータブルなオブジェクト(int、str、tuple など)では「関数内で値を変えても呼び出し元に影響しない」ことが普通です。
- ミュータブルなオブジェクト(list、dict など)の場合は、そのオブジェクト自体を操作すると呼び出し元にも影響が及ぶので注意が必要です。
-
「参照渡しを想定した書き方」は Python ではできない
- C++ や C# のように「引数として渡された変数そのものを再代入し、呼び出し元の変数に反映させる」ような書き方はできません。
- もし「参照渡し」に近い挙動(関数内で再代入したものが呼び出し元の変数そのものを変える)を期待するなら、リストや辞書などミュータブルなオブジェクトを使い、オブジェクト内部 を変更するか、あるいはクラスを使ってオブジェクト同士で状態を共有するといった方法を取る必要があります。
他言語での使い分けとの比較
-
値渡し (call by value)
- プリミティブ型(整数、浮動小数、文字など)でよく利用される。
- 関数内で値を変更しても呼び出し元の変数に影響はない。
-
参照渡し (call by reference)
- 関数内で再代入したりすると、呼び出し元の変数が直接変更される。
- Python ではサポートされていないが、C++ の
int&
、C# のref
などが典型例。
-
Python の場合
- 変数は「オブジェクトを指し示す参照」を保持しており、関数に渡すのは「その参照」を値として渡している。
- 再代入(= 束縛変更)は共有されないが、同じオブジェクトを扱っている限りは変更が呼び出し元にも見える。
- これを「pass by object reference」「pass by assignment」「shared reference」などと呼ぶこともある。
結論
- 一般的に言われる「値渡し」「参照渡し」の定義とは異なり、Python は「オブジェクトへの参照を値として渡す」方式を採用 している。
- そのため、「Python には参照渡しは無い」 という言い方は正しく、かつ「ミュータブルオブジェクトを関数内で操作すると呼び出し元にも変化が及ぶ」という性質も持ち合わせている、独特な仕組みであると理解するとよいでしょう。
- もし「関数内での再代入をそのまま呼び出し元の変数に反映させたい」場合には、Python ではできません。ミュータブルなオブジェクトの内部を操作する、もしくはクラスを用いて状態を共有するといった方法が必要です。
Discussion
それは参照渡しではなくて値渡しでは?
引数に渡した変数(参照が指してるのはこの参照のことで参照型は関係ない)を渡すのが参照渡しなので、その場合、引数に渡した変数を上書きする様な処理を書かないと参照渡しの処理とは言えないのでは?(同じインスタンスを操作できるのは値渡し(この性質から共有渡しともいわれる)では?
Pythonでは参照渡しはないですね。
ご指摘ありがとうございます!!!
関数内で仮引数への代入を行い、 その代入が呼び出し元の実引数の変数に反映されることを望む場合
ですね。
ただ、python にはその機能は無いですが。