グローバル変数は必要です

Life is beautiful: Python Hack : 噛めば噛むほどおいしくなるクロージャの話クロージャと無名関数を使うと、こんな風に実装することができる。

    import yaml

    def _get_from_disk():
         data = open('config.yaml').read().decode('utf8')
         config = yaml.load(data) # クロージャ内に隠蔽・保持されるローカル変数
         global get
         get = lambda : config   # 二回目からはconfigを返す無名関数を呼ぶ様に変更
         return get()

    get = _get_from_disk   # 初回のみローダーを実行

いやいや、それはいけません!

このコードは、config.py というモジュールの一部で、

import config

#config.get()を使って処理

という使い方を想定していますが、こういう使い方も出来てしまいます。

from config import get as config_get

#get()を使って処理

こうすると、config_getの参照先は、config.pyの中でどんな処理をしようと、_get_from_diskのままなので、config_getを呼び出すと毎回ファイルを読み込むことになってしまいます。


同じ問題は、メソッドでも発生します。メソッドオブジェクトを変数に代入した場合です。たとえば、

#!python3
# -*- coding: cp932 -*-

class Config:
    def get(self):
        self.get = lambda : 1 #2回目以降は1を返すつもり
        return 0 # 初回のみ0を返すつもり

c = Config()
get = c.get #メソッドを変数に代入

print(c.get()) # => 0
print(c.get()) # => 1
print(get())   # => 0

というわけで、「初回のみ〜する」場合は、諦めてグローバル変数やメンバ変数を使いましょう。