Pythonにおける引数渡しの実際の動作

Pythonの引数渡しは、CやJavaなどの言語で見られる「値渡し」(pass by value)や「参照渡し」(pass by reference)とは根本的に異なります。Pythonでは、すべての値がオブジェクトであり、変数は単にそのオブジェクトへの名前付き参照に過ぎません。このため、引数渡しは正確には「オブジェクト参照による渡し(pass by object reference)」と呼ばれます。

以下のコードは、不可変オブジェクト(例:整数)の挙動を示します:

original = 42
duplicate = original
original = original + 1

最初、originalduplicate は同じ整数オブジェクト 42 を参照しています。しかし、original = original + 1 は既存のオブジェクトを変更するのではなく、新しい整数オブジェクト 43 を生成し、original をそれに再バインドします。一方、duplicate は依然として 42 を参照したままです。結果として、両者の値は分岐します。

次に、可変オブジェクト(例:リスト)の場合:

source = ["a", "b"]
mirror = source
source.insert(0, "x")

ここで sourcemirror は同一のリストオブジェクトを共有しています。insert() はこのオブジェクトを直接変更するため、mirror からも変更後の状態 ["x", "a", "b"] が観測されます。これは、オブジェクト自体が変化したためであり、参照先のアドレスが変わったわけではありません。

重要な区別は、「オブジェクトの変更」と「変数の再バインド」です。次の関数例でそれが明確になります:

def modify_in_place(container):
    container.append("changed")

def rebind_local(container):
    container = container + ["new"]  # 新しいリストを作成して再バインド

data = [1, 2]
modify_in_place(data)
print(data)  # → [1, 2, "changed"]

data = [1, 2]
rebind_local(data)
print(data)  # → [1, 2](変化なし)

前者の modify_in_place は受け取ったリストオブジェクトに対して破壊的変更を行い、呼び出し元の変数にも影響を与えます。後者の rebind_local はローカル変数 container のみを新しいオブジェクトへ再バインドするため、呼び出し元の data には一切影響しません。

また、del 文は変数のバインディングを解除するだけであり、オブジェクトそのものを削除するものではありません。オブジェクトは、その参照カウントがゼロになった時点でガーベジコレクタによって解放されます。

まとめると、Pythonの引数渡しは:

  • 常に「オブジェクトへの参照」が渡されるが、それはメモリアドレスではなくオブジェクト識別子として機能する;
  • 不可変型(int, str, tuple)は再代入時に必ず新オブジェクトを生成;
  • 可変型(list, dict, set)はメソッド呼び出しで内部状態を直接変更可能;
  • 関数内で「=」による代入は、ローカル変数の再バインドであり、呼び出し元には伝播しない。

タグ: Python object-reference immutable mutable Parameter-Passing

7月7日 18:48 投稿