ProgrammingPython開発者 / データエンジニア

Pythonの関数に可変オブジェクト(リストや辞書など)を渡すと何が起こりますか?関数の内外で予期しない変更を避けるにはどうすればよいですか?

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

答え。

問題の背景:

Pythonにおけるパラメータの渡し方は「オブジェクト参照による呼び出し」(時には共有による呼び出しとも呼ばれます)を実装しています。これは、関数内の変数が外部から渡された引数と同じメモリ内のオブジェクトを指し始めることを意味します。

問題:

関数が渡された可変オブジェクト(例えば、リストや辞書)を変更すると、その変更は関数の外部でも見えるようになります。これは、関数が入力データを変更しないことが期待される場合に、難解なバグを引き起こす可能性があります。

解決策:

副作用を避けるためには、関数内でオブジェクトのコピーを作成するか、不変のデータ構造を使用するべきです。コピーを作成するために、標準的な方法を使用します(例えば、リスト用にはlist.copy()、辞書用にはdict.copy()、またはcopy.deepcopy()を使用します)。

コード例:

def append_one(xs): xs.append(1) return xs lst = [0] append_one(lst) print(lst) # [0, 1] # 変更を避けるには? コピーを作成する: def safe_append_one(xs): ys = xs.copy() ys.append(1) return ys lst2 = [0] safe_append_one(lst2) print(lst2) # [0]

主な特徴:

  • 可変オブジェクトを渡すと、その状態を関数内外で変更できます。
  • これを避けるためにはデータのコピー(浅い/深いコピー)を使用します。
  • 不変オブジェクトはその変更から保護されています。

誤解を招く質問。

.copy()によって生成されたリストのコピーは元のリストから完全に独立していると言えますか?

いいえ — .copy()は浅いコピーを作成します。内部に可変オブジェクトがある場合、その変更は元のオブジェクトにも見えます。

import copy lst = [[1, 2], [3, 4]] shallow = lst.copy() shallow[0][0] = 42 print(lst) # [[42, 2], [3, 4]] deep = copy.deepcopy(lst) deep[0][0] = 100 print(lst) # [[42, 2], [3, 4]]

新しいオブジェクトを元にして返すことは、元のオブジェクトに変更がないことを保証しますか?

必ずしもそうではありません。新しいオブジェクト内で元のオブジェクトの一部(例えば、内部のリストへの参照)を使用している場合、元のオブジェクトが変更される可能性があります。

def duplicate_list(xs): return xs * 2 lst = [[1], [2]] res = duplicate_list(lst) res[0][0] = 999 print(lst) # [[999], [2]]

可変オブジェクトに対するデフォルト引数が、関数の複数回呼び出し時に問題を引き起こすことがありますか?

はい — デフォルト値は関数定義時に一度だけ計算されます。

def add_item(item, container=[]): container.append(item) return container print(add_item(1)) # [1] print(add_item(2)) # [1, 2]

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

  • ユーザーに通知せずに関数内で渡された可変オブジェクトを変更すること。
  • ネストされたデータ構造に対して浅いコピーを適用すること(ミュータブルなネストオブジェクトのエラー)。
  • 関数の引数としてデフォルト値に可変オブジェクトを使用すること。

生活の中の例

ネガティブケース

設定処理ライブラリでは、デフォルト値としてリストを使用したため、関数の異なる呼び出しの間で要素が蓄積されることになりました。その動作は予測できず、非常に長い時間がかかって判明しました。

利点:

再呼び出しのためのコードが少なく、メモリの目に見える節約が得られます。

欠点:

暗黙的な動作、デバッグの難しさ、長期間にわたるバグ。

ポジティブケース

デフォルト値としてNoneを使用し、毎回の呼び出しで新しいオブジェクトを明示的に作成します。

利点:

予測可能性、予期しない副作用の回避、信頼性。

欠点:

意識的である必要があり、少し多くのコードを必要とします。