Programmingバックエンド開発者

Pythonにおける引数の参照渡しと値渡しとは何ですか? Pythonはこのメカニズムをどのように実現しており、関数設計においてそれらを区別することがなぜ重要ですか?

Hintsage AIアシスタントで面接を突破

答え。

Pythonが関数に引数を渡す方法を理解することは、データの予期しない変更を防ぎ、コードを正しく設計するために非常に重要です。

問題の背景

CやJavaなどの従来のプログラミング言語では、値渡し(copy by value)や参照渡し(copy by reference)が使われます。しかし、Pythonは異なるモデルを持っています — オブジェクト参照による呼び出し(時々「共有による呼び出し」と呼ばれます)。

問題

多くの開発者は、Pythonが常に引数を参照として、または値として渡すと思い込んでいます。これにより、変更可能なオブジェクトが予期せず呼び出し元のコードで修正される状況が避けられません。

解決策

Pythonでは、関数のパラメータの値は、関数に渡されるオブジェクトへの参照です。これは次のことを意味します:

  • 変更可能なオブジェクト(mutable: list, dict, set…)の場合、関数内で修正でき、その変更は外部にも反映されます。
  • 変更不可能なオブジェクト(immutable: int, str, tuple, frozenset)の場合、関数内で変更しようとすると新しいオブジェクトが作成され、外部には影響しません。

例:

# list - 変更可能(mutable) def add_item(lst): lst.append(42) my_list = [1, 2, 3] add_item(my_list) print(my_list) # [1, 2, 3, 42] # int - 変更不可能(immutable) def add_num(x): x = x + 1 num = 10 add_num(num) print(num) # 10

主な特徴:

  • 変更可能なオブジェクトは関数内で変更でき、これらの変更は外部に見える。
  • 変更不可能なオブジェクトは関数によって影響を受けず、単に新しいオブジェクトが作成される。
  • Pythonは引数を自動的にコピーしない — 最近接構造も常に「参照として」渡されます。

トラップ問題。

Pythonでは常に引数が参照として渡されますか?

いいえ、Pythonではオブジェクトへの参照が渡され、そのオブジェクトの挙動は、変更可能かどうかに依存します。変更不可能なオブジェクトは変更時に常に新しいオブジェクトが作成されます。

関数内で変更可能な引数を再割り当てし、それを外部のオブジェクトに影響を与えることはできますか?

いいえ。関数内でパラメータに新しい値を割り当てても、外部のオブジェクトには何も変わりません — あなたは単にローカル参照を変更しているだけです。

例:

def reassign_list(lst): lst = [99, 100] my_list = [1, 2, 3] reassign_list(my_list) print(my_list) # [1, 2, 3]

デフォルトでlistを受け取る関数が再呼び出しで奇妙に動作するのはなぜですか?

デフォルト値は関数定義時に一度だけ作成され、その後変更されると(例えば、要素を追加するなど)、すべての後続の呼び出しで変更されます。

def add_element(x, cache=[]): cache.append(x) return cache print(add_element(1)) # [1] print(add_element(2)) # [1, 2]

一般的なエラーとアンチパターン

  • デフォルトの変更可能な引数を使用する(最後の例のように)。
  • 関数が渡されたlistやdictを変更しないと期待しているが、実際にはそれを修正する。
  • 変更可能なオブジェクトと変更不可能なオブジェクトを扱う際に、関数の効果を混同する。

実生活の例

ネガティブケース

プログラマーは関数にリストを渡し、元のリストが変更されないことを期待しますが、関数は要素を追加します。

利点:

  • 高速な操作で、データのコピーが不要です。

欠点:

  • 予期しない副作用、大きなコードにバグが発生する可能性がある、もし誰かが引数の修正を知らない場合。

ポジティブケース

プログラマーは、元のリストを変更せずに何かを返す必要があるときに、関数内で明示的にリストをコピーします:

def process_data(data): data = data.copy() # または list(data) # コピーで安全に作業 data.append('レポート') return data

利点:

  • 不要な副作用がなく、元のデータが保護されています。

欠点:

  • 大きなオブジェクトの場合、コピーに対するメモリ/時間のコストがかかる。