Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

python ひとり暗黒魔法勉強会 暗黒魔法でAOP

benjyo ni import de AOP wo kakeru sample ga kaiteattayo!
もっと黒魔術的にAOP
importするモジュールに対してAOPしてみる。
例はモジュール内のクラスの"__"から始まってないメソッドに対しロギングする。

weave.py
import sys, types

class LoggerAspect(object):

    def __init__(self, method):
        self.method = method

    def before(self):
        print 'entering', self.method

    def after(self, retval, exc):
        print 'retvale', retval
        print 'leaving', self.method

def weave_method(method, advice_class, *advice_args, **advice_kwargs):
    advice = advice_class(method, *advice_args, **advice_kwargs)
    advice_before = advice.before
    advice_after = advice.after

    def invoke_advice(*args, **kwargs):
        advice_before()
        try:
            retval = method(*args, **kwargs)
        except Exception, e:
            advice_after(None, e)
            raise
        else:
            advice_after(retval, None)
            return retval

    try:
        class_ = method.im_class
    except:
        method.func_globals[method.func_name] = invoke_advice
    else:
        setattr(class_, method.__name__, invoke_advice)
    return invoke_advice

def weave(mod):
    
    for k,v in mod.__dict__.items():
        if not k.startswith('__'):
            if not isinstance(v, types.FunctionType) and not isinstance(v, types.ModuleType):
                _class_weave(v)

def _class_weave(clazz):
    for key, value in clazz.__dict__.items():
        if not key.startswith('__') and isinstance(value, types.FunctionType):
            #logging example
            weave_method(getattr(clazz, key), LoggerAspect)

resolverなしなのでお好みのresolverを実装してちょ。

aop.py
import sys, weave, imp, __builtin__


def import_hook(name, globals=None, locals=None, fromlist=None):
    parent = determine_parent(globals)
    q, tail = find_head_package(parent, name)
    m = load_tail(q, tail)
    if not fromlist:
        return q
    if hasattr(m, "__path__"):
        ensure_fromlist(m, fromlist)
    return m

def determine_parent(globals):
    if not globals or  not globals.has_key("__name__"):
        return None
    pname = globals['__name__']
    if globals.has_key("__path__"):
        parent = sys.modules[pname]
        assert globals is parent.__dict__
        return parent
    if '.' in pname:
        i = pname.rfind('.')
        pname = pname[:i]
        parent = sys.modules[pname]
        assert parent.__name__ == pname
        return parent
    return None

def find_head_package(parent, name):
    if '.' in name:
        i = name.find('.')
        head = name[:i]
        tail = name[i+1:]
    else:
        head = name
        tail = ""
    if parent:
        qname = "%s.%s" % (parent.__name__, head)
    else:
        qname = head
    q = import_module(head, qname, parent)
    if q: return q, tail
    if parent:
        qname = head
        parent = None
        q = import_module(head, qname, parent)
        if q: return q, tail
    raise ImportError, "No module named " + qname

def load_tail(q, tail):
    m = q
    while tail:
        i = tail.find('.')
        if i < 0: i = len(tail)
        head, tail = tail[:i], tail[i+1:]
        mname = "%s.%s" % (m.__name__, head)
        m = import_module(head, mname, m)
        if not m:
            raise ImportError, "No module named " + mname
    return m

def ensure_fromlist(m, fromlist, recursive=0):
    for sub in fromlist:
        if sub == "*":
            if not recursive:
                try:
                    all = m.__all__
                except AttributeError:
                    pass
                else:
                    ensure_fromlist(m, all, 1)
            continue
        if sub != "*" and not hasattr(m, sub):
            subname = "%s.%s" % (m.__name__, sub)
            submod = import_module(sub, subname, m)
            if not submod:
                raise ImportError, "No module named " + subname

def import_module(partname, fqname, parent):
    try:
        return sys.modules[fqname]
    except KeyError:
        pass
    try:
        fp, pathname, stuff = imp.find_module(partname,
                                              parent and parent.__path__)
    except ImportError:
        return None
    try:
        m = imp.load_module(fqname, fp, pathname, stuff)
        weave.weave(m)
    finally:
        if fp: fp.close()
    if parent:
        setattr(parent, partname, m)
    return m


# Replacement for reload()
def reload_hook(module):
    name = module.__name__
    if '.' not in name:
        return import_module(name, name, None)
    i = name.rfind('.')
    pname = name[:i]
    parent = sys.modules[pname]
    return import_module(name[i+1:], name, parent)


# Save the original hooks
original_import = __builtin__.__import__
original_reload = __builtin__.reload

# Now install our hooks
__builtin__.__import__ = import_hook
__builtin__.reload = reload_hook

import対象のモジュール

hoge.py
class Hoge(object):

    def test(self, str):
        return str

実行モジュール

test.py
import aop
import hoge


t = hoge.Hoge()
t.test("TEST")

この場合hogeモジュールに対してAOPをかける。

import aop

AOPをかけるモジュールのimportより先にある必要がある。
あと自分自身に対してAOPかけたい場合、__name__が__main__の場合は先述の自前でaopの関数を呼ぶ方法で対処可能かな。
っつーか__name__が__main__の状態でAOPつーのもどうかな。
あとこれを応用するとsingleton化とかも黒魔術で解決できる。

とりあえずこいつさえあれば他の本なんていらない気がする。

Pythonクィックリファレンス

Pythonクィックリファレンス

うくく。