跳到主要内容

python装饰器

· 4 分钟阅读

在看python相关教程时,碰到介绍装饰器(decorator)相关章节很费了一番脑筋,不知道是因为翻译不达意还是个人理解偏差的缘故,总是感觉难以理解。有的教程上说装饰器在不改变原始函数(方法)和类的情况下对其进行装饰功能,本身是一个返回可调用对象的可调用对象。这个说法显得有些绕口,在python中,常用的可调用对象基本是function和class,这就意味着装饰器本身可以为二者之一;对于方法和类,调用时可接受一些参数,而针对装饰器,则对传入的参数有一定的限定:方法或类,并在内部做完相关的工作后,返回对应的方法或类。

例如,想知道某些函数的调用情况,可以定义一个counter的函数装饰器,记录对应的调用次数,然后返回原函数:

>>> count=0
>>> def counter(func):
... def counternumber(*args,**kwargs):
... global count
... count+=1
... print("The %s times to %s" %(count,func.__name__))
... return func(*args,**kwargs)
... return counternumber
...
>>> @counter
... def sum(x,y):
... print(x+y)
...
>>> sum(1,2)
The 1 times to sum
3
>>> sum(3,4)
The 2 times to sum
7
>>> @counter
... def multiply(x,y):
... print(x*y)
...
>>> multiply(1,2)
The 3 times to multiply
2
>>> multiply(4,5)
The 4 times to multiply
20

以上代码开看起来是实现了相关功能,但由于装饰器内部的counternumber函数存在,导致改变了被装饰函数的函数名,此部分相信是大部分人不愿见到的。

>>> print(sum.__name__)
counternumber
>>> multiply.__name__
'counternumber'
>>>

可以看到,所有被装饰的函数名均被替换成了装饰器内部的函数。那么,有没有办法可以不改变被装饰的函数名呢,答案是肯定的。参见如下示例代码:

>>> class counter:
... def __init__(self,func):
... self.func=func
... self.__name__=func.__name__
... self.count=0
... def __call__(self,*args,**kwargs):
... self.count+=1
... print("The %s times to %s" %(self.count,self.__name__))
... return self.func(*args,**kwargs)
...
>>> @counter
... def f(x):
... return x
...
>>> f(1)
The 1 times to f
1
>>> f('fun')
The 2 times to f
'fun'
>>> f.__name__
'f'
>>>

这次的counter是一个类,但可以用来装饰函数f,在常规用法中,装饰器本身可以是方法或类,被装饰的对象也可以是方法或类。带来好处是可以在不影响被装饰对象的情况下,附着一些其他功能。

另:在Python内置的functools.wraps可以解决被装饰对象属性变化的此类问题,此处不再详细介绍。