python2中的类方法钩子装饰器

时间:2017-01-01 15:56:56

标签: python

我在使用'post hook'函数装饰本机列表方法时遇到问题。我需要一个装饰器,它将本地列表方法作为参数,并且在调用该方法之后,它还应该运行正在装饰的函数代码。 这段代码可以正常工作:

def _add_hook(clsmtd, info_fcn):
    def hooked(s, *p, **k):
        ret = clsmtd(s, *p, **k)
        '''some magic happens here'''
        print info_fcn(s, *p, **k)
        return ret
    return hooked

def reporting_list_deco(cls):
    def append_info(s, *p, **k):
        return 'append to %s' % s._name
    cls.append = _add_hook(cls.append, append_info)

    def remove_info(s, idx, *p, **k):
        return 'removin %s[%s]' % (s._name, idx) 
    cls.remove = _add_hook(cls.remove, remove_info)

    def setitem_info(s, idx, *p, **k):
        return 'setitem %s[%s]' % (s._name, idx)
    cls.__setitem__ = _add_hook(cls.__setitem__, setitem_info)
    ''' and so on also for pop, sort, insert, reverse and extend '''
    return cls

def test_reporting_list():
    @reporting_list_deco
    class reportin_list(list):
        def __init__(self, *p, **k):
            super(reportin_list, self).__init__(*p, **k)
            self._name = 'foo'
    rl = reportin_list([6,6,6])
    rl[1] = 1
    rl.append(7)
    rl.remove(1)
    ''' outputs:
        setitem foo[1]
        append to foo
        removin foo[1]
    '''

但我想这样写:

def better_reporting_list_deco(cls):
    @_add_hook_for(cls.append)
    def append_info(s, *p, **k):
        return 'append to %s' % s._name

    @_add_hook_for(cls.remove)
    def remove_info(s, idx, *p, **k):
        return 'removin %s[%s]' % (s._name, idx) 

    @_add_hook_for(cls.__setitem__)
    def setitem_info(s, idx, *p, **k):
        return 'setitem %s[%s]' % (s._name, idx)

    return cls

问题在于我不知道如何编写_add_hook_for装饰器。请指教。

2 个答案:

答案 0 :(得分:3)

写一个装饰工厂。你传入类,因为你无法可靠地检索它(你从类传递方法,所以即使我们设法检索我们使用错误的目标类的上下文:

def _add_hook_for(cls, target):
    def hook_decorator(hook):
        def hooked(s, *p, **k):
            ret = target(s, *p, **k)
            # some magic happens here
            print hook(s, *p, **k)
            return ret
        setattr(cls, target.__name__, hooked)
        return hook
    return hook_decorator

然后你的班级装饰师变成:

def better_reporting_list_deco(cls):
    @_add_hook_for(cls, cls.append)
    def append_info(s, *p, **k):
        return 'append to %s' % s._name

    @_add_hook_for(cls, cls.remove)
    def remove_info(s, idx, *p, **k):
        return 'removing %s[%s]' % (s._name, idx) 

    @_add_hook_for(cls, cls.__setitem__)
    def setitem_info(s, idx, *p, **k):
        return 'setitem %s[%s]' % (s._name, idx)

    return cls

演示:

>>> @better_reporting_list_deco
... class reporting_list(list):
...     def __init__(self, *p, **k):
...         super(reporting_list, self).__init__(*p, **k)
...         self._name = 'foo'
...
>>> rl = reporting_list([6, 6, 6])
>>> rl[1] = 1
setitem foo[1]
>>> rl.append(7)
append to foo
>>> rl.remove(1)
removing foo[1]

答案 1 :(得分:0)

感谢@ martijn-pieters,我重写了它(在Python 2.7中运行良好):

def _add_hook_for(cls, target):
    def hook_decorator(hook):
        def hooked(s, *p, **k):
            ret = target(s, *p, **k)
            # some magic happens here
            print(hook(s, target.__name__, *p, **k))
            return ret
        setattr(cls, target.__name__, hooked)
        return hook
    return hook_decorator

def reporting_list_deco(cls):
    @_add_hook_for(cls, cls.append)
    @_add_hook_for(cls, cls.extend)
    @_add_hook_for(cls, cls.sort)
    @_add_hook_for(cls, cls.reverse)
    def no_idx_info(s, op, *p, **k):
        return '%s %s' % (op, s._name)

    @_add_hook_for(cls, cls.__setitem__)
    @_add_hook_for(cls, cls.insert)
    @_add_hook_for(cls, cls.__delitem__)
    @_add_hook_for(cls, cls.remove)
    @_add_hook_for(cls, cls.pop)
    def idx_info(s, op, idx='', *p, **k):
        return '%s %s[%s]' % (op, s._name, idx) 
    return cls

def test_reporting_list():
    @reporting_list_deco
    class reporting_list(list):
        def __init__(self, name, *p, **k):
            super(reporting_list, self).__init__(*p, **k)
            self._name = name

    rl = reporting_list('foo', [61,62,63])
    rl[1] = 1
    rl.append(7)
    rl.remove(1)
    rl.extend([5,6,7])
    rl.pop()
    rl.pop(2)
    rl.sort()
    del rl[0]
    rl.reverse()
    print(rl)

输出:

__setitem__ foo[1]
append foo
remove foo[1]
extend foo
pop foo[]
pop foo[2]
sort foo
__delitem__ foo[0]
reverse foo
[63, 61, 6] 

它也可以应用于词典。谢谢Martijn!