最近看到两篇写的非常好的知识文章:如何理解Python装饰器,理解Python装饰器(Decorator),对我理解python中的装饰器有非常大的作用。现将其记录下来,方便以后温故知新。
装饰器概念
和装饰器离不开的是一种叫闭包的概念。
闭包
首先看如下代码段:
# print_msg是外围函数
def print_msg():
msg = "I'm closure"
# printer是嵌套函数
def printer():
print(msg)
return printer
# 这里获得的就是一个闭包
closure = print_msg()
# 输出 I'm closure
closure()
msg是一个局部变量,在print_msg函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。维基百科中对闭包的定义如下:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
结合这个例子再看维基百科的解释,就清晰明了多了。闭包就是引用了自有变量的函数,就如例子中的外函数和内函数组合成一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
装饰器
装饰器一般有如下形式:
def decorator(func):
# 传入的参数自定义
def wrapper(*args, **kargs):
do something……
# 传入了什么方法,就返回那个方法
return func(*args, **kargs)
# 返回内层函数
return wrapper
装饰器的使用方法可分两种:使用语法糖@或者不用。
# 使用语法糖
@decorator
def f(*args, **kargs)
pass
f(*args, **kargs)
# 不使用语法糖
def f(*args, **kargs)
pass
x = decorator(f)
x(*args, **kargs)
从上面可以看出,@语法只是把我们定义好的函数f传到装饰器函数decorator里,这样执行函数f的时候就会变成执行decorator了。
装饰器举例
下面是一个显示函数名字的装饰器和调用情况:
import functools
# 装饰器本体
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('call %s():' % func.__name__)
print('args = {}'.format(*args))
return func(*args, **kwargs)
return wrapper
# 装饰器包装函数
@log
def test(p):
print(test.__name__ + " param: " + p)
# 调用包装过的函数
test("I'm a param")
# 结果如下
call test():
args = I'm a param
test param: I'm a param
其中有一个 @functools.wraps(func) 字样的玩意是python自带的装饰器。它能把原函数的元信息拷贝到装饰器括号里面的函数中,包括docstring、name、参数列表等等。因为我们的test函数是输出自己的函数名,所以functools.wraps传入的函数是func自己。可以尝试去除 @functools.wraps(func),会发现test.__name__的输出变成了wrapper。
另外,原例子中如果不用@语法使用自定义装饰器的话,不用上面那个python自带装饰器也能让test.__name__的输出为test自己。
进阶装饰器:带参数传入
在上面的例子中,装饰器只是包装了函数,在包装时没有传入参数。那么传入参数的装饰器是什么样的呢?只需要在原来的基础上再加一层包装即可。一个例子如下:
def outside(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('call %s():' % func.__name__)
print('args = {}'.format(*args))
print('log_param = {}'.format(text))
return func(*args, **kwargs)
return wrapper
return decorator
@ outside("这是外面的参数")
def test1(p):
print(test1.__name__ + " param: " + p)
test1("这是参数")
# 不用@的话这样使用装饰器:
x = (outside("这是外面的参数"))(test)
x("这是参数")
# 不用@也可以这么写
(outside("外面的参数"))(test1)
(outside("外面的参数"))(test1)("里面的参数")
理解装饰器
从上面的用法可以看出,装饰器的用法是在保证不修改原函数的基础上增加他的功能,让我们的编程更加高效。
番外:内置装饰器
常见的内置装饰器有三种,@property、@staticmethod、@classmethod
property装饰器
装饰器不仅可以装饰函数,还能装饰类。该装饰器把类内方法当成属性来使用,必须要有返回值,相当于getter;假如没有定义 @方法名.setter 修饰方法的话,就是只读属性。
class Car:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def car_name(self):
return self._name
# 让car_name可以读写
@car_name.setter
def car_name(self, value):
self._name = value
# car_price是只读属性
@property
def car_price(self):
return str(self._price) + '万'
benz = Car('benz', 30)
print(benz.car_name) # benz
benz.car_name = "baojun"
print(benz.car_name) # baojun
print(benz.car_price) # 30万
在上面的例子中,汽车有两个属性,名字和价钱,还有返回相对应属性的方法。使用了@property和@car_name.setter的car_name方法可以像属性一样被调用而且支持读写属性;只使用@property的方法car_price则只能去读取属性。