考虑这个小例子:
import datetime as dt
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
class Test(object):
def __init__(self):
super(Test, self).__init__()
@Timed
def decorated(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
return dict()
def call_deco(self):
self.decorated("Hello", world="World")
if __name__ == "__main__":
t = Test()
ret = t.call_deco()
哪个打印
Hello
()
{'world': 'World'}
为什么self
参数(应该是Test obj实例)没有作为第一个参数传递给装饰函数decorated
?
如果我手动进行操作,例如:
def call_deco(self):
self.decorated(self, "Hello", world="World")
它按预期工作。但是,如果我必须事先知道某个函数是否装饰,它就破坏了装饰器的全部目的。这里的模式是什么,还是我误会了什么?
tl; dr
您可以通过将Timed
类作为描述符并返回部分应用的函数来解决此问题,该函数从中__get__
应用Test
对象作为参数之一,如下所示
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
实际问题
引用Python文档作为 decorator ,
装饰器语法只是语法糖,以下两个函数定义在语义上是等效的:
def f(...): ... f = staticmethod(f) @staticmethod def f(...): ...
所以,当你说
@Timed
def decorated(self, *args, **kwargs):
它实际上是
decorated = Timed(decorated)
只有函数对象传递给了Timed
, 实际绑定到的对象并没有传递给它 。因此,当您像这样调用它时
ret = self.func(*args, **kwargs)
self.func
将引用未绑定的函数对象,并将其Hello
作为第一个参数调用。这就是为什么self
打印为的原因Hello
。
我怎样才能解决这个问题?
由于您在中没有引用Test
实例Timed
,因此唯一的方法是将其转换Timed
为 描述符类
。引用文档,“调用描述符”部分,
一般而言,描述符是与“结合行为”,一个属性的访问已被覆盖通过在描述符协议方法的对象属性:
__get__()
,__set__()
,和__delete__()
。如果为对象定义了这些方法中的任何一种,则称其为描述符。属性访问的默认行为是从对象的字典中获取,设置或删除属性。例如,
a.x
有一个查找链,从a.__dict__['x']
,然后到type(a).__dict__['x']
,并一直到type(a)
排除元类的基类。但是, 如果查找到的值是定义描述符方法之一的对象,则Python可能会覆盖默认行为并改为调用描述符方法 。
我们可以Timed
通过简单地定义一个这样的方法来制作一个描述符
def __get__(self, instance, owner):
...
在这里,self
是指Timed
对象本身,instance
是指在其上发生属性查找的实际对象,并且owner
是指与对应的类instance
。
现在,在__call__
上调用时Timed
,该__get__
方法将被调用。现在,以某种方式,我们需要将第一个参数传递为Test
class的实例(甚至在之前Hello
)。因此,我们创建了另一个部分应用的函数,其第一个参数将是Test
实例,如下所示
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
现在,self.__call__
是一个绑定方法(绑定到Timed
实例),第二个参数partial
是self.__call__
调用的第一个参数。
所以,所有这些都像这样有效翻译
t.call_deco()
self.decorated("Hello", world="World")
现在self.decorated
实际上是Timed(decorated)
(TimedObject
从现在开始将被称为)对象。每当我们访问它时,其中__get__
定义的方法都会被调用并返回一个partial
函数。您可以像这样确认
def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")
会打印
<functools.partial object at 0x7fecbc59ad60>
...
所以,
self.decorated("Hello", world="World")
被翻译成
Timed.__get__(TimedObject, <Test obj>, Test.__class__)("Hello", world="World")
由于我们返回了一个partial
函数,
partial(TimedObject.__call__, <Test obj>)("Hello", world="World"))
实际上是
TimedObject.__call__(<Test obj>, 'Hello', world="World")
因此,它<Test obj>
也成为的一部分*args
,并且在self.func
被调用时,第一个参数将是<Test obj>
。
本文向大家介绍基于Python 装饰器装饰类中的方法实例,包括了基于Python 装饰器装饰类中的方法实例的使用技巧和注意事项,需要的朋友参考一下 title: Python 装饰器装饰类中的方法 comments: true date: 2017-04-17 20:44:31 tags: ['Python', 'Decorate'] category: ['Python'] --- 目前在中文网
我有一个小项目,用一个类包装另一个类的对象。修饰类实现了一个接口,但装饰类没有实现它。我很好奇它仍然是装饰模式还是其他模式,在我的项目中“装饰”类应该被称为包装器而不是装饰器。 我已经检查了iluwatar github存储库(https://github.com/iluwatar/java-design-patterns/tree/master/decorator/src/main/java/c
Django为视图提供了数个装饰器,用以支持相关的HTTP服务。 允许的HTTP 方法 django.views.decorators.http 包里的装饰器可以基于请求的方法来限制对视图的访问。若条件不满足会返回 django.http.HttpResponseNotAllowed。 require_http_methods(request_method_list)[source] 限制视图只能
装饰器(Decorators)(被babel支持, 在 03/17 之后作为stage-2的proposal被引入) 如果你在使用类似于mobx的库, 你能够使用装饰器装饰你的函数. 装饰器本质上其实就是将组件传入一个函数. 使用装饰器能让组件更灵活,更可读并且更易修改组件的功能. 不使用装饰器的例子 class ProfileContainer extends Component { //
上一篇文章将通过解决一个需求问题来了解了闭包,本文也将一样,通过慢慢演变一个需求,一步一步来了解 Python 装饰器。 首先有这么一个输出员工打卡信息的函数: def punch(): print('昵称:两点水 部门:做鸭事业部 上班打卡成功') punch() 输出的结果如下: 昵称:两点水 部门:做鸭事业部 上班打卡成功 然后,产品反馈,不行啊,怎么上班打卡没有具体的日