关于函数:了解类装饰器如何在Python中工作

Understanding how class decorators work in Python

我对理解Python中的类修饰符如何工作有一个问题。在本例中,我想编写一个修饰符,它计算调用递归函数(搜索最大公因数)的次数。我有个装修师:

1
2
3
4
5
6
7
8
9
10
11
12
class TrackCalls(object):

def __init__(self, func):
    self.func = func
    self.calls = 0

def __call__(self,*args,**kwargs):
    self.calls += 1
    return self.func(*args, **kwargs)

def called(self):
    return self.calls

和一个函数:

1
2
3
4
5
6
7
8
9
@TrackCalls
def NWD(a, b):

    if a > b:
        return NWD(a-b, b)
    elif b > a:
        return NWD(a, b-a)
    else:
        return a

然后我叫他们:

1
2
print(NWD(60,25)) #5
print(NWD.called()) #6

nwd函数到底发生了什么?据我所知,修饰符取了一个函数并生成了另一个函数,所以在这种情况下,trackcalls取了这个函数,生成了一个trackcalls类的对象,然后通过调用NWD.called()我基本上调用了trackcalls对象的方法?当我在之前的调用后运行NWD(5,25)时,我得到11个,所以每次调用nwd时,我都用某种静态变量调用trackcalls对象。如果我用相同的修饰器修饰另一个递归函数,它们会共享calls变量吗?


我认为你的困惑可以追溯到python的duck输入概念。这是指从外部的角度来看,使用__call__方法的类的function和对象(实例)之间没有区别。两者都是callable,也就是说,它们可以在括号内接受参数,返回的内容与func(args)obj(args)中的内容相同。原因是不同的是,作为一个对象,obj有状态,也就是说它有实例变量(也常称为字段)。

在您的示例中,callsfuncTrackCalls类的字段。通过用@TrackCalls修饰NWD函数,可以用TrackCalls的实例包装NWD。也就是说,NWD的名称基本上被一个TrackCalls的实例替换,相当于调用NWD = TrackCalls(NWD)。新实例是可调用的,因此其行为类似于原始函数,但由于它是一个对象,因此它也具有状态,并且可以计算其__call__方法的调用。计算本身委托给NWD的原始实现,该实现现在存储在func字段中。

为了回答你的问题,你用TrackCalls装饰的每一个功能都会产生一个新的TrackCalls实例,每个都有自己的状态。但是对一个修饰函数的多个调用共享该函数的状态。