fabricのenvって辞書が頭良かったので再実装してみた

fabric は Python製のデプロイなどを行うスクリプト

公式ドキュメントは以下
参考::Fabric — Fabric 1.4.4 pre-alpha (76f466d) documentation
あとは @IanMLewis さんのドキュメントが詳しい
参考::Fabric デプロイツールのPythonicな書き方 - Ian Lewis
で、ここでもでてくるとおりに from fabric.api import env という環境を設定できる辞書のようなものがある。これが不思議で普通 dict なら dct[key] = value としなければならないのに、 env.key = value のようにできているから不思議だった。たしかに env.key = value だと簡単だ。なのでこれの fabric の実装を見てみたら案外単純な仕組みで「ほえー」となったのでかく。

インストール

pip でいいですね

pip install fabric

今回は 1.4.3 がはいった。
virtualenv環境はある前提だと、例えば ~/.virtualenvs/myvertualenvs/lib/python2.7/site-packages/fabric とかにある。

コードリーディングする

具体的にはまず from fabric.api import env とあるので env.py をみてみる

...
from fabric.state import env, output
...

なるほど、今度は state.py を探す。

...
from fabric.utils import _AliasDict, _AttributeDict
...
env = _AttributeDict({
    'again_prompt': 'Sorry, try again.',
    'all_hosts': [],
    ...
    ...
    'user': None,
    'version': get_version('short')
})
...

なるほど、 _AttributeDict ってのが実態なのか。 utls.py をみてみる。

class _AttributeDict(dict):
    ...
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            # to conform with __getattr__ spec
            raise AttributeError(key)

    def __setattr__(self, key, value):
        self[key] = value

    def first(self, *names):
        for name in names:
            value = self.get(name)
            if value:
                return value

これがその env.key = value でアクセスできる実態か。なるほど、再実装してみる。

再実装してみた

class ExDict(dict):
    """デフォルトの辞書を . でアクセスできるように拡張した"""
    def __getattr__(self, key):
        """属性を呼び出すときに呼ばれる"""
        print 'getattr'
        try:
            return self[key]
        except KeyError:
            raise AttributeError(key)

    def __setattr__(self, key, value):
        """属性に代入したときに呼ばれる"""
        print 'setattr'
        self[key] = value
        print self

env = ExDict()
env.a = 'aaa'
print env.a
env.b = 'bbb'
print env.b
print env.c

実行結果

# まずは値を代入操作が呼ばれた
setattr
# self でインスタンスをみてみる。辞書ですね
{'a': 'aaa'}
# 値を取り出す操作が呼ばれた
getattr
# print env['a'] と同じ
aaa
# もうひとつ値を代入する操作が呼ばれた
setattr
# 辞書に追加されているのがわかる
{'a': 'aaa', 'b': 'bbb'}
# 値を取り出す操作が呼ばれた
getattr
# print env['b'] と同じ
bbb
# 存在しない key を取り出してみる
getattr
# print env['c'] と同じ
# 当然存在しないので except で落ちる
Traceback (most recent call last):
  File "/Users/atasatamatara/a.py", line 19, in <module>
    print env.c
  File "/Users/atasatamatara/a.py", line 7, in __getattr__
    raise AttributeError(key)
AttributeError: c

なるほどなー。辞書を継承したクラスを作って、そのインスタンスを env とする。で、それの __getattr__ と __setattr__ で擬似的に辞書に格納するようにしている。 __getattr__ はそのインスタンスのプロパティが呼ばれた時に発動する組み込みの関数、また逆に __setattr__ はインスタンスのプロパティに代入したときに発動する組み込みの関数だ。ここらへんに関する公式ドキュメントは 3.4.2 属性値アクセスをカスタマイズする

おもしろかった

こういうメタプログラミングっぽい(メタプログラミングよくわかんない)ことを駆使するのが ruby なのかなー。それはそれでおもしろそうだしめんどくさそうでもある。まあ自分はメタメタしいことはまだあんまできなくていいや。普通のことが普通にやれるようになりたい。「普通のプログラマー」のレベルってハードル高いっす。