LoginSignup
2
1

More than 5 years have passed since last update.

venusian

Last updated at Posted at 2013-11-14

デコレータの実行を遅延させる仕組み。デコレートされた関数のテストがしやすくなったりする。
メタ情報を付与するだけのアノテーションのようにデコレータを使うことができそう。

pyramidのview_config関数で使用されている。
http://docs.pylonsproject.org/projects/venusian/en/latest/

使い方

  1. デコレータ関数を定義するときに、vensuian.attachを呼び出す。

    # theframework.py
    
    import venusian
    
    def jsonify(wrapped):
        def callback(scanner, name, ob):
                    def jsonified(request):
                      result = wrapped(request)
                      return json.dumps(result)
                scanner.registry.add(name, jsonified)
            venusian.attach(wrapped, callback)
        return wrapped
    
  2. 1の関数をデコレータとして呼び出す

    # theapp.py
    
    from theframework import jsonify
    
    @jsonify
    def logged_in(request):
            return {'result':'Logged in'}
    

    jsonify関数の実態(callback関数)はまだ呼ばれていない。

  3. スキャンする

    import venusian
    import theapp
    
    class Registry(object):
         def __init__(self):
              self.registered = []
    
         def add(self, name, ob):
             self.registered.append((name, ob))
    
    registry = Registry()
    scanner = venusian.Scanner(registry = registry)
    scanner.scan(theapp)
    

    この時点で、callback関数が呼ばれ、scanner.registryにjsonfiy関数でデコレートされたlogged_in関数が入ることになる。
    カテゴリごとにわけてscanすることなどもできる。(ドキュメント参照)

実装(ざっくり)

attach

パッケージ

venusian/init.py

実装

def attach(wrapped, callback, category=None, depth=1):
    """ Attach a callback to the wrapped object.  It will be found
    later during a scan.  This function returns an instance of the
    :class:`venusian.AttachInfo` class."""

wrappedの__venusian_callbacks__メンバに、callback関数を登録する。
callback関数のホルダーにはCategoriesクラスを使っている。

# カテゴリ名をキー、コールバック関数のリストを値として持つクラス。
class Categories(dict):
    def __init__(self, attached_to):
        super(dict, self).__init__()
        if attached_to is None:
            self.attached_id = None
        else:
            self.attached_id = id(attached_to)

    def attached_to(self, obj):
        if self.attached_id:
            return self.attached_id == id(obj)
        return True

流れ

  1. sys._getframeしてwrappedが関数なのかクラスなのかなどを判定
  2. wrappedの__venusian_callbacks__メンバに値が設定されていなければ、Categoriesインスタンスを新たに生成し、設定。
  3. Categoriesインスタンスに対して、引数categoryをキーにcallbackを登録する。複数登録できるようlistになっている。
  4. Categoriesインスタンスやwrappedの情報を含んだAttachInfoクラスを生成して返す。

Scanner.scan

パッケージ

venusian/init.py

実装

class Scanner(object):
    ...
    def scan(self, package, categories=None, onerror=None, ignore=None):
        """ Scan a Python package and any of its subpackages.  All
        top-level objects will be considered; those marked with
        venusian callback attributes related to ``category`` will be
        processed.
        ...

attachされたオブジェクトに登録されたcallback関数を呼び出す。どれを対象とするかはcategoryやignoreで指定可能。

流れ

  1. packageに対してinspect.getmembersし、packageに所属するメンバーを取得。
  2. 各メンバーオブジェクトに対して内部関数invokeを適用。ここでcallbackが呼ばれる。

    def invoke(mod_name, name, ob):
       """
        mod_name: scan中のモジュール名
        name: getmembersで得られたオブジェクト名
        ob: nameに対応するオブジェクト
        """
    
    1. ignoreに指定されたオブジェクトでないかチェック。指定されたオブジェクトならreturn。
    2. __venusian_callbacks__メンバ(attachで登録されたCategoryインスタンス)があり、なおかつそのCategoryインスタンスが持つattached_toメンバがobと等しければ次に進む。
    3. callback呼び出しの対象となるcategoryを決める。scan関数のcategoriesパラメータが指定されていなければ、登録されているcategoryをすべて対象に含める。
    4. 各categoryに紐付いたcallback関数を取得し、実行する。
  3. packageが__path__を持つか検証。持っていなければreturn

  4. packageに対してwalk_package関数を呼び出し、そのパッケージに属するモジュールのモジュールローダーのイテレータを取得する。
    ※この関数はpkgutil.walk_packagesとほぼ同じだが、ignoreを指定させたいので実装している。

  5. walk_packageの結果新しい未importのモジュールが見つかればimportし、そのモジュールに対してinspect.getmembers, invokeを行う。

感想

デコレートされたオブジェクトのメタ情報を結局はその関数自身にもたせている。(__venusian_callbacks__メンバ)

この辺は動的にメンバーを追加できる言語ならではか。言語として関数にメタ情報を含ませる仕組みがあるといいと思うのだけど。

[追記]
python3のアノテーションの仕組みと共存できるようなやり方ができればいいかも。
func.annotationsに関数自身のメタ情報を持てるようにする、とか。
python3のアノテーションは引数と戻り値に関するメタ情報は持てるが、それ以外の情報(例えばjavaの@Deprecatedとか)は持てない。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1