Pythonにおける引数渡し:誤解されがちな「参照渡し」の真実と特徴

ログラミングを学ぶ上で、「参照渡し」や「値渡し」という言葉はよく耳にするかもしれません。
しかし、Pythonにおける引数の渡し方は、他の多くの言語とは少し異なり、しばしば誤解を招くことがあります。

ここでは、Pythonが採用している引数渡しの仕組みについて、その特徴と、なぜ「参照渡し」と「値渡し」という言葉だけでは説明しきれないのかを解説します。

Pythonの引数渡しは「オブジェクトの参照渡し(Call by Object Reference)」

まず結論から言うと、Pythonは厳密な意味での「参照渡し(call by reference)」や「値渡し(call by value)」を採用していません。
その代わりに、「オブジェクトの参照渡し(call by object reference)」、あるいは
「代入渡し(call by assignment)」
と呼ばれる独自の仕組みを持っています。

これは、関数に引数を渡す際、常に
「オブジェクトそのものではなく、そのオブジェクトがメモリ上のどこにあるかを示す情報(参照)のコピー」
が渡される、ということを意味します。変数はオブジェクトへの「ラベル」や「名前」のようなものであり、そのラベルが指し示す「場所」の情報がコピーされて関数に渡される、と考えると分かりやすいでしょう。

オブジェクトの「変更可能性(Mutable/Immutable)」が挙動を分ける

この「オブジェクトの参照渡し」の仕組みは、Pythonのオブジェクトが持つ
「ミュータブル(変更可能)」「イミュータブル(変更不可能)」
という特性によって、関数内での操作が関数外の元のオブジェクトに影響するかどうかが変わります。

イミュータブル(Immutable:変更不可能)なオブジェクトの場合

  • 例: 数値(int, float)、文字列(str)、タプル(tuple)など
  • これらのオブジェクトは一度作成されると、その中身を変更することはできません。

関数にイミュータブルなオブジェクトの参照のコピーが渡され、関数内でその引数の「値を変更しようと再代入」した場合、実際には新しいオブジェクトが作成されます。
引数の変数はその新しいオブジェクトを指すようになりますが、元の変数は引き続き元のオブジェクトを指しているため、関数外の元の変数の値は変わりません。

def modify_immutable(x):
    # x に新しい値(オブジェクト)を再代入
    x = x + 10
    print(f"関数内での x の値: {x}")

my_number = 5
modify_immutable(my_number)
print(f"関数外での my_number の値: {my_number}")
# 出力:
# 関数内での x の値: 15
# 関数外での my_number の値: 5 (変更なし)

この挙動は、結果的に「値渡し」のように見えます。
my_numberとxは別々のオブジェクトを指すことになります。

ミュータブル(Mutable:変更可能)なオブジェクトの場合

例: リスト(list)、辞書(dict)、セット(set)、クラスのインスタンスなど
これらのオブジェクトは、作成後にその中身(要素など)を変更することができます。

関数にミュータブルなオブジェクトの参照のコピーが渡された場合、引数も元の変数も同じオブジェクトへの参照を共有しています。
そのため、関数内でその引数を通してオブジェクトの「中身(要素など)」を変更すると、元のオブジェクト自体が変更されます。

def modify_mutable(my_list):
    # my_list が指すリストオブジェクトの中身を変更
    my_list.append(4)
    print(f"関数内での my_list の値: {my_list}")

my_data = [1, 2, 3]
modify_mutable(my_data)
print(f"関数外での my_data の値: {my_data}")
# 出力:
# 関数内での my_list の値: [1, 2, 3, 4]
# 関数外での my_data の値: [1, 2, 3, 4] (変更された)

この挙動は、結果的に「参照渡し」のように見えます。my_dataとmy_listは同じオブジェクトを指しています。

注意点:ミュータブルなオブジェクトでも「再代入」すると元のオブジェクトには影響しない
ミュータブルなオブジェクトを渡した場合でも、関数内で引数の変数に**新しいオブジェクトを「再代入」した場合は、関数外の元の変数には影響しません。**これは、引数の変数が新しいオブジェクトを指すようになっただけで、元の変数は変わらず元のオブジェクトを指し続けているためです。

def reassign_mutable(my_list):
    # my_list に新しいリストオブジェクトを再代入
    my_list = [5, 6, 7]
    print(f"関数内での my_list の値 (再代入後): {my_list}")

original_data = [1, 2, 3]
reassign_mutable(original_data)
print(f"関数外での original_data の値 (再代入後): {original_data}")
# 出力:
# 関数内での my_list の値 (再代入後): [5, 6, 7]
# 関数外での original_data の値 (再代入後): [1, 2, 3] (変更なし)

まとめ Pythonの引数渡しを正しく理解する重要性

Pythonの引数渡しは、他の言語のように単純に「値渡し」か「参照渡し」かで割り切れるものではなく、
「オブジェクトの参照が値として渡される」という特性と、
オブジェクト自身の「変更可能性(ミュータブル/イミュータブル)」
の組み合わせで理解することが重要です。

この違いを正しく理解することで、関数内でのオブジェクトの振る舞いを予測し、意図しないバグを防ぐことができます。特に、ミュータブルなオブジェクトを関数に渡す際は、関数内で中身が変更される可能性があることを意識してコーディングすることが、堅牢なプログラムを作成する上で不可欠です。

【訂正履歴】2025年6月16日:
「Pythonにおける引数渡しの仕組みについて、いくつかの誤解を招く表現や不正確な点がありました。
読者の皆様にご迷惑をおかけいたしました。今後、より正確な情報発信に努めてまいります。

最初のコメントをしよう

必須