{}const=>[]async()letfn</>var
開発Python

なぜあなたのPythonコードは奇妙に振る舞うのですか?mutable default arguments の罠

関数に以前の呼び出しを「記憶」させる、最も陰湿なPythonエラーの1つを分析します。空のリストが突然非空になった理由と、デフォルトの引数を正しく操作する方法を学びます。

К

Kodik

著者

1分で読める

問題はどのようなものですか?

タスクをタスクリストに追加するための関数を書いていると想像してみてください。

def add_task(task, todo_list=[]):
    todo_list.append(task)
    return todo_list

# 最初のタスクを追加しますprint (add_task ("ミルクを買う")) # ['ミルクを買う']クを追加しますprint(add_task("犬を散歩させる"))  # 期待: ['犬を散歩させる']                             # 結果は次のとおりです。 ['牛乳を買う', '犬を散歩させる']

⚠️ 何が起こったのか? 1つのタスクを含む新しいリストを受け取ることを期待していましたが、2つ目のタスクが最初のタスクに追加されました。しかも、前のリストはどこにも保存していません。魔法?いいえ、これはmutable default argumentsです。

🔥 10万人以上の学生が参加中

理論を読むのに疲れた?
コーディングの時間だ!

Kodik — 実践でプログラミングを学ぶアプリ。AIメンター、インタラクティブなレッスン、実際のプロジェクト。

🤖 AI 24時間
🎓 修了証
💰 無料
🚀 始める
今日参加

なぜこのようなことが起こるのでしょうか?

Pythonが関数を作成するとき、デフォルトでオブジェクトを作成します 1回 関数が定義されたときに、関数が呼び出されるたびにではなく。このオブジェクトは保存され、次の呼び出しごとに再利用されます。

実際に何が起こっているのか見てみましょう。

def add_task(task, todo_list=[]):
    todo_list.append(task)
    print(f"List ID: {id(todo_list)}")
    return todo_list

add_task("First task")   # リストID:140234567891234add_task (" 2番目のタスク") #リストID:140234567891234 (同じものです!) add_task (" 3番目のタスク") #リストID:140234567891234 (また同じものです!)

💡 重要なポイント: 毎回同じリストオブジェクトが使用されます。すべての関数呼び出しはメモリ内の同じリストで動作するため、すべての変更が累積されます。

どのような種類のデータが影響を受けますか?

この問題は、Pythonのすべての可変データ型に関係します。これには、リスト、辞書、セット、カスタムオブジェクトが含まれます。しかし、数値、文字列、タプル(不変タイプ)は変更できず、新しいものを作成することしかできないため、正常に動作します。

# 辞書付きの問題コードdef add_user(name, users_dict={}):
    users_dict[name] = True
    return users_dict

print(add_user("Alexey"))  # {'Alexey': True}print(add_user("Maria")) # {'Alexey': True, 'Maria': True} - 両方ともここにあります!# 文字列を含む安全なコード (immutable)def greet(name, greeting="こんにちは"):    greeting = greeting + ", " + name
    return greeting

print(greet("Ivan"))   # こんにちは、Ivanprint(greet("オルガ")) # こんにちは、オルガ - すべて期待通りに動作します

正しい選択です。

この問題の古典的な解決策は、Noneをデフォルト値として使用し、関数内に新しいオブジェクトを作成することです。

def add_task(task, todo_list=None):
    if todo_list is None:
        todo_list = []
    todo_list.append(task)
    return todo_list

# これで、すべてが正しく機能しますprint(add_task("牛乳を買う"))        # ['牛乳を買う']print(add_task("犬を散歩させる"))   # ['犬を散歩させる']

✅ ルール: リストを渡さないと、新しい空のリストが作成されます。問題が解決しました!

辞書でも同じように機能します。

def create_profile(name, data=None):
    if data is None:
        data = {}
    data['name'] = name
    data['created_at'] = 'today'
    return data

profile1 = create_profile("Alexey")
profile2 = create_profile("Maria")

print(profile1)  # {'name': 'アレクセイ', 'created_at': '今日'}print(profile2)  # {'name': 'マリア', 'created_at': '今日'}

いつ役に立ちますか?

興味深いことに、この動作は、たとえば結果をキャッシュするために、特別に使用できることがあります。

def get_config(config_cache={}):
    if 'data' not in config_cache:
        print("Loading configuration...")
        config_cache['data'] = "important settings"
    return config_cache['data']

print(get_config())  # 構成を読み込んでいます... 重要な設定print (get_config ())  # 重要な設定(読み込みはありません)

💡 注意: しかし、実際のコードでは、キャッシュには functools.lru_cache のような特別なツールを使用することをお勧めします。これにより、コードがよりわかりやすくなります。

このミスを避けるにはどうすればよいですか?

単純なルールを覚えておいてください。関数の引数のデフォルト値として、変更可能なオブジェクト(リスト、辞書、セット)を使用しないでください。常に None を使用し、関数内に必要なオブジェクトを作成してください。

このエラーは非常に一般的であるため、多くのリント(コード検証ツール)は特にこのエラーについて警告しています。PyCharm、pylint または flake8 を使用した VSCode を使用している場合、このようなコードを書こうとすると警告が表示されます。

初めてのPythonを正しく学びましょう!

このトピックやその他の重要なトピックについては、 コディケ - すべてのニュアンスを明確な説明で理解し、興味深いタスクで実践的に強化します。各テーマには実際の開発例が添えられており、実践的な課題は得られた知識をすぐに応用するのに役立ちます。

学習中に質問があったり、複雑な点について話し合いたい場合は、 2000人の志を同じくする人々 アクティブ Telegramチャンネル、プログラミングの道を理解し、サポートするのに常に役立ちます!

🎯先延ばしをやめよう

記事は気に入った?
実践の時間だ!

Kodikでは読むだけでなく、すぐにコードを書く。理論 + 実践 = 本当のスキル。

即座に実践
🧠AIがコードを説明
🏆修了証

登録不要 • カード不要