我是python decorators的新手。我借助简单的例子理解了基本概念。但是当我试图阅读这个更实用的装饰时,我感到迷茫。下面是我的问题后面的代码:
class countcalls(object):
"Decorator that keeps track of the number of times a function is called."
__instances = {}
def __init__(self, f):
self.__f = f
self.__numcalls = 0
countcalls.__instances[f] = self
def __call__(self, *args, **kwargs):
self.__numcalls += 1
return self.__f(*args, **kwargs)
def count(self):
"Return the number of times the function f was called."
return countcalls.__instances[self.__f].__numcalls
@countcalls
def f():
print 'f called'
f()
f()
f()
print f.count() # prints 3
我的怀疑:
当我们将装饰器添加到函数前面时,这是否意味着我们正在创建装饰器类的对象?在我们的例子中,当它说:
@countcalls
def f():
print 'f called'
@countcalls
是否等同于创建countcalls
对象并将下面的函数传递给其__init__
方法?
__call__
有三个参数。只要上述问题得到解答,self
就可以了。另外两个论点到底是什么:*args, **kwargs
他们实现了什么?
我怎样才能更好地装饰?
答案 0 :(得分:5)
这段代码似乎有些奇怪。我们来谈谈稍微简单的代码
class countcalls(object):
def __init__(self, f):
self._f = f
self._numcalls = 0
def __call__(self, *args, **kwargs):
self._numcalls += 1
return self._f(*args, **kwargs)
def count(self):
return self._numcalls
@countcalls
def f():
print 'f called'
f()
f()
f()
print f.count()
# output:
# f called
# f called
# f called
# 3
记住
@countcalls
def f():
print 'f called'
与
相同def f():
print 'f called'
f = countcalls(f)
所以当我们使用装饰器时,使用_f
属性存储该函数。
所以f
是countcalls
个实例。当您执行f(...)
时,请致电f.__call__(...)
- 这就是您为自己的实例实施()
语法的方式。所以当你致电f
时会发生什么?
def __call__(self, *args, **kwargs):
self._numcalls += 1
return self._f(*args, **kwargs)
首先,使用*args
和**kwargs
,它在定义中将所有位置和关键字参数压缩为元组和字典,稍后在调用中将序列和dict扩展为参数(请参阅4.7.4在官方教程中获取更多信息)。这是一个部分示例
>>> def f(*args): print args
...
>>> f(1, 2)
(1, 2)
>>> f()
()
>>> def add(a, b): return a + b
...
>>> add(*[4, 3])
7
>>> add(**{'b': 5, 'a': 9})
14
所以def f(*args, **kwargs): return g(*args, **kwargs)
只对所有参数进行直通。
除此之外,您只是跟踪此实例__call__
的次数(您调用f
的次数)。
请记住,@dec
def f(...): ...
与def f(...): ...
f = dec(f)
相同,如果有足够的时间,您应该能够找出大多数装饰器。像所有事情一样,练习将帮助您更快更轻松地完成这项工作。
答案 1 :(得分:3)
当我们将装饰器添加到函数前面时,这是否意味着我们正在创建装饰器类的对象?
有一种非常简单的方法可以找到答案。
>>> @countcalls
... def f(): pass
>>> f
<a.countcalls object at 0x3175310>
>>> isinstance(f, countcalls)
True
所以,是的。
@countcalls是否等同于创建一个countcalls对象并将下面的函数传递给它的 init 方法?
几乎。它相当于那个然后将结果分配给函数的名称,即
def f():
print 'f called'
f = countcalls(f)
我怎样才能更好地装饰?
答案 2 :(得分:2)
相当于以下内容:
f = countcalls(f)
换句话说:是的,我们正在创建一个countcalls
对象并将f
传递给它的构造函数。
这些是函数参数。在您的情况下,f
不接受任何参数,但假设f
被调用如下:
f(3, 4, keyword=5)
然后*args
将包含3和4,**kwargs
将包含键/值对keyword=5
。有关*args
和**kwargs
的详细信息,请参阅this question。
实践变得完美。
答案 3 :(得分:0)
是的,但需要注意的是,它还会将该对象绑定到def中给出的名称。
了解它们,阅读示例,与其中一些玩。