5

我是 python 装饰器的新手。在简单示例的帮助下,我已经理解了基本概念。但是当我尝试阅读这个更实用的装饰器时,我感到很失落。下面给出的是我的问题后面的代码:

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'

是否@countcalls相当于创建一个countcalls对象并将下面的函数传递给它的__init__方法?

  1. __call__是采取三个论点。self只要回答了上面的问题就可以了。另外两个论点*args, **kwargs到底是什么:它们的目的是什么?

  2. 我怎样才能在装饰师方面做得更好?

4

4 回答 4

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)

首先,您使用*argsand **kwargs,它在定义中将所有位置和关键字参数压缩为一个元组和字典,然后在调用中将一个序列和一个字典展开为参数(有关更多信息,请参见官方教程中的 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)足够的时间,您应该能够很好地找出大多数装饰器。像所有事情一样,练习会帮助你更快更容易地做到这一点。

于 2012-06-26T20:40:47.260 回答
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
于 2012-06-26T20:11:39.193 回答
2
  1. 它等价于以下内容:

    f = countcalls(f)

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

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

    f(3, 4, keyword=5)

    然后*args将包含 3 和 4 并**kwargs包含键/值对keyword=5。有关*argsand的更多信息**kwargs,请参阅此问题

  3. 熟能生巧。

于 2012-06-26T20:11:50.127 回答
0
  1. 是的,需要注意的是它还将该对象绑定到定义中给出的名称。

  2. 阅读它们,阅读示例,玩一些。

于 2012-06-26T20:11:23.847 回答