2017-03-08 154 views
3

虽然有plenty of resources about using classes as decorators,但我一直未能找到任何处理方法的问题。这个问题的目标是解决这个问题。我会发布我自己的解决方案,但当然也邀请其他人发布他们的解决方案。使用类作为方法装饰器


为什么“标准”的实施不起作用

与标准的装饰类实现的问题是,蟒蛇不会造成装饰功能的绑定方法:

class Deco: 
    def __init__(self, func): 
     self.func= func 

    def __call__(self, *args): 
     self.func(*args) 

class Class: 
    @Deco 
    def hello(self): 
     print('hello world') 

Class().hello() # throws TypeError: hello() missing 1 required positional argument: 'self' 

方法装饰者需要克服这个障碍。


要求

从前面的例子中以类,下面的事情也有望可以使用:

>>> i= Class() 
>>> i.hello() 
hello world 
>>> i.hello 
<__main__.Deco object at 0x7f4ae8b518d0> 
>>> Class.hello is Class().hello 
False 
>>> Class().hello is Class().hello 
False 
>>> i.hello is i.hello 
True 

理想的情况下,该功能的__doc__和签名以及类似的属性将被保留为好。

+0

也相关:[Python装饰最佳实践,使用类与函数](http://stackoverflow.com/questions/10294014/python-decorator-best-practice-using-a-class-vs-a-功能) –

+0

为什么你需要它是一个类?装饰者只是一个函数有什么问题? –

+0

@PaulRooney在我的特殊情况下(我正在编写一个GUI库),我想在函数中存储一些属性(如键盘热键,描述,类别等)以及一些函数(比如''。 start_in_new_thread()','.update_status()')。不是强制所有这些属性到函数上,而是编写一个包装类并完全替换该函数。 –

回答

2

基本“什么都不做”装饰类:

import inspect 
import functools 
from copy import copy 


class Deco: 
    def __init__(self, func): 
     self.__self__ = None # "__self__" is also used by bound methods 

     functools.update_wrapper(self, func) 

    def __call__(self, *args, **kwargs): 
     # if bound to an object, pass it as the first argument 
     if self.__self__ is not None: 
      args = (self.__self__,) + args 

     #== change the following line to make the decorator do something == 
     return self.__wrapped__(*args, **kwargs) 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self 

     # create a bound copy 
     bound = copy(self) 
     bound.__self__ = instance 

     # update __doc__ and similar attributes 
     functools.update_wrapper(bound, self.__wrapped__) 

     # add the bound instance to the object's dict so that 
     # __get__ won't be called a 2nd time 
     setattr(instance, self.__wrapped__.__name__, bound) 

     return bound 

,而另一种参数:

class DecoWithArgs: 
    #== change the constructor's parameters to fit your needs == 
    def __init__(self, *args): 
     self.args = args 

     self.__wrapped__ = None 
     self.__self__ = None 

    def __call__(self, *args, **kwargs): 
     if self.__wrapped__ is None: 
      return self.__wrap(*args, **kwargs) 
     else: 
      return self.__call_wrapped_function(*args, **kwargs) 

    def __wrap(self, func): 
     # update __doc__ and similar attributes 
     functools.update_wrapper(self, func) 

     return self 

    def __call_wrapped_function(self, *args, **kwargs): 
     # if bound to an object, pass it as the first argument 
     if self.__self__ is not None: 
      args = (self.__self__,) + args 

     #== change the following line to make the decorator do something == 
     return self.__wrapped__(*args, **kwargs) 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self 

     # create a bound copy of this object 
     bound = copy(self) 
     bound.__self__ = instance 
     bound.__wrap(self.__wrapped__) 

     # add the bound decorator to the object's dict so that 
     # __get__ won't be called a 2nd time 
     setattr(instance, self.__wrapped__.__name__, bound) 
     return bound 

这样的实现让我们使用的方法装饰以及功能,所以我认为这应该被认为是很好的做法。