Python経験者が「おっ」と思える、よく使われるけど誤解されやすい機能をテーマに、短くて深いサンプルコードをいくつか紹介します。
コードだけでなく、なぜ「なるほど」なのかの解説付きです。
✅ 1. defaultdict の「list」と「set」の違い
from collections import defaultdict
a = defaultdict(list)
b = defaultdict(set)
a['x'].append(1)
b['x'].add(1)
a['x'].append(1)
b['x'].add(1)
print(a['x']) # => [1, 1]
print(b['x']) # => {1}
💡 ポイント
-
defaultdict(list)は重複を許す(リスト) -
defaultdict(set)はユニーク化される(集合) - 「defaultdictって何がデフォルト?」を意識できるかが経験者ポイント
✅ 2. is と == の違い(Pythonオブジェクトの真髄)
a = 256
b = 256
print(a is b) # True
a = 257
b = 257
print(a is b) # False
💡 ポイント
- Python は -5〜256 までの整数を **インターン(キャッシュ)**してる
-
isは同一オブジェクトかどうか(IDの比較) -
==は値の等価比較
経験者でも
a is bが常に True だと誤解しがち
✅ 3. 引数のデフォルト値がミュータブルな場合
def append_to(val, lst=[]):
lst.append(val)
return lst
print(append_to(1)) # => [1]
print(append_to(2)) # => [1, 2] ←えっ?
💡 ポイント
- デフォルト引数は関数定義時に1回だけ評価される
- つまり
lst=[]は再利用されるオブジェクト
🧠 回避法:
def append_to(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
✅ 4. ジェネレータ式とリスト内包表記の違い
squares = (x * x for x in range(5))
print(next(squares)) # => 0
print(sum(squares)) # => 30 (残りの1^2 + 2^2 + 3^2 + 4^2)
squares2 = [x * x for x in range(5)]
print(sum(squares2)) # => 30 (全要素がある)
💡 ポイント
-
(...)→ ジェネレータ式(遅延評価) -
[...]→ リスト内包(即時評価)
✅ 5. 関数のクロージャとループ変数の罠
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # => [2, 2, 2] ←意図と違う
💡 ポイント
-
lambdaは変数iへの参照を保持している(定義時に値を閉じない)
🧠 対策(デフォルト引数を使って閉じ込める):
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
print([f() for f in funcs]) # => [0, 1, 2]
✅ 6. *args と **kwargs のアンパックの裏技
def greet(greeting, name):
print(f"{greeting}, {name}!")
args = ("Hello", "Alice")
kwargs = {"greeting": "Hi", "name": "Bob"}
greet(*args) # => Hello, Alice!
greet(**kwargs) # => Hi, Bob!
💡 ポイント
-
*args→ 位置引数のアンパック -
**kwargs→ キーワード引数のアンパック - 関数合成やデコレーターで多用される
✅ 7. クラスの属性共有の罠(インスタンス vs クラス変数)
class Counter:
count = 0
def increment(self):
self.count += 1
c1 = Counter()
c2 = Counter()
c1.increment()
print(c1.count) # => 1
print(c2.count) # => 0 ← あれ?
print(Counter.count) # => 0 ← クラス変数はそのまま
💡 ポイント
-
self.count += 1はインスタンスに新しくcount属性を作ってしまう - クラス変数を意図して変更するなら
Counter.count += 1
✅ まとめ
| テーマ | 内容 |
|---|---|
is vs ==
|
値の等価性とオブジェクトの同一性 |
defaultdict |
リストとセットのデフォルト動作の違い |
| ミュータブルなデフォルト引数 | 共通バグパターン、必ず None 初期化で避ける |
| クロージャと変数キャプチャ |
lambda i=i: テクニック |
| ジェネレータ vs リスト内包 | メモリ効率と動作の違い |
*args / **kwargs
|
関数設計の柔軟性 |
| クラス変数の罠 | Python OOPでよくある落とし穴 |