Life is beautiful: Python Hack : 噛めば噛むほどおいしくなるクロージャの話
の解法に疑問があったので、自分なりに書き直してみます。
get関数で設定を読み込めて、読み込んだ設定はキャッシュします。 グローバル変数を使っているあたりがブサイクです。
書き直すのはこのconfigモジュール。
#config import yaml _config = None def get(): global _config if not _config: data = open('config.yaml').read().decode('utf8') _config = yaml.load(data) return _config
Life is beautiful: Python Hack : 噛めば噛むほどおいしくなるクロージャの話
では、クロージャを使うと良いとして、次のコードを提示しています。
#config 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 # 初回のみローダーを実行
このコードの問題は、from config import get や 高階関数で
getを別の変数に代入した時に起きます。
設定ファイルを読み込む例だと説明しづらいので、
乱数をキャッシュするモジュールを考えてみます。
#random_cache import random #初回の乱数をキャッシュして、2回目以降は同じ値を返す def _get_from_random(): cache = random.random() global get get = lambda : cache return get() get = _get_from_random
$>python
Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from random_cache import get as rc_get
>>> rc_get()
0.37072846241539548
>>> rc_get()
0.12122267986050828 #あれー!?
これは、get = lambda : cache という代入が、
"random_spam 内の get変数の中身" に変更を与える操作だからです。
別の変数であるrc_getには関係ないのです。
では、どう書き直せばいいかと言うと、クラスを使えばよいと思います。
#config2.py class Config(object): def _get_config(self): data = open('config.yaml').read().decode('utf8') return yaml.load(data) def cached_get(self): return self.config def get(self): self.config = self._get_config() self.get = self.cached_get return self.config _Config = Config() def get(): return _Config.get()
config2は、標準ライブラリのturtleの様に、
モジュールの関数を直接呼び出す事も、インスタンス化してメソッドを呼ぶ事ができます。