「Pythonって簡単そう!」と始めたのに、気づいたら「え、なんでこうなるの…?」という沼にハマっていた経験はありませんか?
Pythonは確かに読みやすくて書きやすい言語ですが、初心者がよく引っかかる「罠」がいくつか存在します。今回はその中でも特に面白くて、ちょっとクセになる「躓きポイント」を深掘りしていきます!
「あるある!」と笑いながら読めて、読み終わったら確実にレベルアップできる記事を目指しました。ぜひ最後まで楽しんでください😄
🐍 その1:ミュータブルなデフォルト引数の罠

まず最初の罠は、関数のデフォルト引数にリストや辞書を使うケースです。これ、知らないとマジで頭を抱えます。
問題のコードを見てみよう
# ❌ 初心者がやりがちなNG例
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
print(add_item("りんご")) # ['りんご']
print(add_item("バナナ")) # ['バナナ'] を期待したのに...
print(add_item("みかん")) # ['みかん'] を期待したのに...
# 実際の出力:
# ['りんご']
# ['りんご', 'バナナ'] ← え?
# ['りんご', 'バナナ', 'みかん'] ← なんで!?
「なんか前に追加したやつが残ってる!バグじゃないの?」と感じますよね。でもこれはPythonの仕様です。
理由:デフォルト引数はモジュールが読み込まれたときに一度だけ評価されます。つまり `[]` は関数が呼ばれるたびに新しく作られるのではなく、同じリストオブジェクトがずっと使い回されるのです!
正しい書き方はこれ
# ✅ 正しい書き方:デフォルトをNoneにする
def add_item(item, my_list=None):
if my_list is None:
my_list = [] # 関数が呼ばれるたびに新しいリストを作成!
my_list.append(item)
return my_list
print(add_item("りんご")) # ['りんご']
print(add_item("バナナ")) # ['バナナ']
print(add_item("みかん")) # ['みかん']
# 期待通りの出力になった!🎉
覚え方は簡単。「デフォルト引数にリスト・辞書・集合を直接書くな!」です。`None` をデフォルトにして、関数内で初期化するのがPythonのベストプラクティスです。
🔢 その2:整数の「同一性」が予想外の挙動をする
次はちょっとトリッキーな話。Pythonには「小さい整数のキャッシュ」という仕組みがあります。これを知らないと `==` と `is` の違いでハマります。
# 😲 Pythonの不思議な整数の世界
a = 256
b = 256
print(a is b) # True ← 同じオブジェクト!
x = 257
y = 257
print(x is y) # False ← 別のオブジェクト!
# え、256と257で何が違うの...?
Pythonは -5〜256 の整数をあらかじめキャッシュ(メモリ上に保持)しています。この範囲の整数は常に同じオブジェクトが返されるため `is` で比較すると `True` になります。
一方、257以上の整数はキャッシュされないため、同じ値でも別のオブジェクトとして扱われます。
教訓:値の比較には `==` を使いましょう。`is` はオブジェクトの同一性(同じメモリ上のものか)を調べるためのもの。None との比較以外で `is` を使う場面はほぼありません!
🔄 その3:forループの変数がループ外でも生き続ける
C言語やJavaから来た人は特にびっくりする挙動がこれです。
# 😮 ループの外でもiが生きている!
for i in range(5):
pass
print(i) # 4 ← ループが終わってもiにアクセスできる!
# リスト内包表記では別の話...
result = [x for x in range(5)]
# print(x) # ← これはNameError!内包表記内の変数は外に漏れない
Pythonの `for` ループはブロックスコープを作りません。つまり `for i in …` の `i` はループが終わってもそのスコープ(関数やモジュールレベル)に残り続けます。
面白いのはリスト内包表記との違い。内包表記はPython 3から独自のスコープを持つようになったため、内包表記内の変数は外部に漏れません。
これを利用したよくあるバグがこちら:
# ❌ クロージャとループの組み合わせ罠
funcs = []
for i in range(3):
funcs.append(lambda: i) # iを参照しているが...
print(funcs[0]()) # 2 ← 0を期待したのに!
print(funcs[1]()) # 2 ← 1を期待したのに!
print(funcs[2]()) # 2 ← これは合ってるけど...
# ✅ 修正版:デフォルト引数でiをキャプチャ
funcs = []
for i in range(3):
funcs.append(lambda i=i: i) # デフォルト引数でその時点のiを束縛
print(funcs[0]()) # 0 🎉
print(funcs[1]()) # 1 🎉
print(funcs[2]()) # 2 🎉
lambdaはループ変数 `i` を「参照」しているため、ループが終わったときの最後の値(2)を使ってしまいます。デフォルト引数でその時点の値をキャプチャするのがテクニックの一つです!
📋 その4:コピーしたつもりがコピーじゃない!(浅いコピーと深いコピー)
「リストをコピーしたのに、元のリストが変わってしまった!」これも初心者が必ず一度はハマる罠です。
import copy
# ❌ 参照のコピー(同じオブジェクトを指している)
original = [1, 2, 3]
copied = original # これはコピーじゃない!
copied.append(4)
print(original) # [1, 2, 3, 4] ← 元も変わってしまった!
# ✅ 浅いコピー(ネストなしならOK)
original = [1, 2, 3]
shallow = original.copy() # または list(original) や original[:]
shallow.append(4)
print(original) # [1, 2, 3] ← 変わらない!
# ⚠️ 浅いコピーの落とし穴(ネストしたリストは要注意)
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0].append(99) # 内側のリストを変更すると...
print(original) # [[1, 2, 99], [3, 4]] ← 元も変わってしまった!
# ✅ 深いコピー(ネストがあるときはこれ)
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2], [3, 4]] ← 完全に独立している!
Pythonでは変数はオブジェクトへの「参照(リファレンス)」を持っています。`copied = original` はリストのコピーではなく、同じリストへの参照をもう一つ作るだけです。
ポイントをまとめると:
- フラットなリスト →
.copy()で浅いコピーでOK - ネストしたリスト・辞書など →
copy.deepcopy()で深いコピーを使う
🐛 その5:「なんで同じコードなのに動かないの?」インデントの悪夢
最後はPython特有のインデント(字下げ)問題です。「そんなの基本じゃん」と思うかもしれませんが、スペースとタブの混在はプロでもやらかす罠です。
# ⚠️ こんなエラーが出たら要注意!
# TabError: inconsistent use of tabs and spaces in indentation
def greet(name):
print(f"こんにちは、{name}さん!") # スペース4つでインデント
print(f"今日もいい天気ですね!") # タブでインデント ← ここが問題!
# Python 3はスペースとタブの混在を厳しくチェックする!
# Python 2では動いていたコードがPython 3で動かないケースもある
# ✅ 解決策:エディタで「タブをスペースに変換」を設定する
# VS Codeなら右下の「スペース:4」または「タブサイズ:4」を確認!
対策として、エディタの設定で「タブキーをスペース×4に変換」する設定を必ずONにしておきましょう。VS CodeやPyCharmなどの主要エディタには標準でこの機能が搭載されています。
また、既存のコードを整形するには autopep8 や black などのフォーマッターを使うのがおすすめです!
📊 まとめ:Python初心者の5大躓きポイント比較表
今回紹介した躓きポイントを一覧でまとめました!
| 躓きポイント | 原因 | 対策 | 難易度 |
|---|---|---|---|
| ミュータブルなデフォルト引数 | デフォルト値が一度しか評価されない | デフォルトを None にする | ★★☆ |
| 整数の同一性(isの罠) | -5〜256がキャッシュされる | 値の比較は == を使う | ★★★ |
| ループ変数のスコープ | Pythonにブロックスコープがない | 変数名の管理と理解 | ★★☆ |
| 浅いコピーと深いコピー | 変数が参照を持つ仕組み | ネスト時は copy.deepcopy() | ★★★ |
| スペースとタブの混在 | 見た目が同じでも内部が違う | エディタでタブをスペースに変換 | ★☆☆ |
🎯 今日のポイント3選
- デフォルト引数にリスト・辞書は使わない。Noneをデフォルトにして関数内で初期化しよう!
- コピーは「参照」「浅いコピー」「深いコピー」の3種類を理解しよう。ネストがあるなら
copy.deepcopy()が安心! - 比較には
==を使い、isはNoneとの比較専用と覚えよう。
Pythonの「あれ、なんで?」は理解してしまえば「なるほど!」に変わります。今回紹介した罠は、ベテランでも一度はハマったことがある定番ネタです😂
「自分もこの罠にハマったことある!」「こんな罠もあるよ!」という体験談があれば、ぜひコメントで教えてください。みんなで罠を共有してPythonマスターを目指しましょう💪





