无法理解这个python装饰器

时间:2012-06-26 20:06:58

标签: python decorator

我是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

我的怀疑:

  1. 当我们将装饰器添加到函数前面时,这是否意味着我们正在创建装饰器类的对象?在我们的例子中,当它说:

    @countcalls    def f(): print 'f called'

  2. @countcalls是否等同于创建countcalls对象并将下面的函数传递给其__init__方法?

    1. __call__有三个参数。只要上述问题得到解答,self就可以了。另外两个论点到底是什么:*args, **kwargs他们实现了什么?

    2. 我怎样才能更好地装饰?

4 个答案:

答案 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属性存储该函数。

所以fcountcalls个实例。当您执行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)
  

我怎样才能更好地装饰?

  1. 实践
  2. 阅读其他人使用它们的代码
  3. 研究相关的PEP

答案 2 :(得分:2)

  1. 相当于以下内容:

    f = countcalls(f)

    换句话说:是的,我们正在创建一个countcalls对象并将f传递给它的构造函数。

  2. 这些是函数参数。在您的情况下,f不接受任何参数,但假设f被调用如下:

    f(3, 4, keyword=5)

    然后*args将包含3和4,**kwargs将包含键/值对keyword=5。有关*args**kwargs的详细信息,请参阅this question

  3. 实践变得完美。

答案 3 :(得分:0)

  1. 是的,但需要注意的是,它还会将该对象绑定到def中给出的名称。

  2. 了解它们,阅读示例,与其中一些玩。