
Python: 関数オブジェクトに直接変数を生やす技(クロージャ不要の『状態保持』)
Pythonで「関数が呼び出された回数をカウントしたい」といった状態を保持する処理を書く際、通常はクラスを作成するか、クロージャ(nonlocal)やグローバル変数(global)を使うのが一般的です。
しかしPythonでは、関数そのものに直接プロパティ(属性)を生やして数値を保持させる という裏技が存在します。
まずは、直感的かつシンプルに動くこちらのコードをご覧ください。
Python
def counter():
counter.count += 1
return counter.count
# 👁️ 関数オブジェクトに対して動的に属性(count)を追加する!
counter.count = 0
print(counter()) # 結果: 1
print(counter()) # 結果: 2counter という関数名に対して、まるで通常のクラスインスタンスであるかのように .count = 0 と動的属性を追加し、関数内部でそれを更新・更新保持しています。
原因:Pythonでは「関数もただのオブジェクト」だから
なぜこんな芸当ができるのでしょうか?
その理由は、Pythonにおける関数が「第一級オブジェクト(First-class object)」 であり、本質的には通常のクラスオブジェクトと何ら変わりない構造を持っているからです。
Pythonの関数オブジェクト内部には __dict__ という辞書が保持されており、外部から counter.count = 0 と記述すると、その関数の __dict__ に動的に属性が追加されます。
そのため、クラスをインスタンス化する手足を省き、「特定の関数内だけで完結するコンパクトな状態保持 mechanism」 として機能するのです。
応用と注意点
1. 静的変数(C言語の static 変数)のような使い方ができる
関数と状態が密結合になるため、「その関数専用のキャッシュ」や「呼び出しカウンター」を作る際にグローバル空間を汚染せずに済みます。
2. 注意点:初期化を忘れると AttributeError になる
関数を一度呼び出す前に counter.count = 0 という属性定義を行っていないと、関数実行時に AttributeError: 'function' object has no attribute 'count' で即死します。
初期化を関数内部で安全に行いたい場合は、以下のように hasattr() を組み合わせる設計が安全です。
Python
def safe_counter():
if not hasattr(safe_counter, "count"):
safe_counter.count = 0
safe_counter.count += 1
return safe_counter.count「関数もまた、属性を持てるひとつのオブジェクトである」。
Pythonのオブジェクト指向の自由度の高さを感じるこの仕様を知っておくと、スクリプト作成やちょっとした状態管理で役立ちます!
#Python #プログラミング #エンジニア #バックエンド #プログラミング初心者 #バグ #学び #オブジェクト指向