在介绍Python的装饰器之前,先得介绍介绍装饰模式(Decorator)。修饰模式主要是动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
而正式因为装饰模式非常有用,所以Python提供了原生支持。在Python语言中,函数与方法都可以用装饰器来修饰。此外,还有”类装饰器”(class decorator),它也是个单参数的函数,其参数是类,由这种装饰器所返回的新类的名称与原类相同,但功能更多。有时可以通过类装饰器来实现继承。
函数装饰器与方法装饰器
创建一个装饰器的流程:
1. 创建”包装函数”(wrapper function, 我们多以wrapper()来命名)。
2. 在包装函数里调用原函数。调用前可以进行预处理,获取结果之后,还可以执行后加工。包装函数的返回值也很灵活:可以把原函数的调用结果直接返回,也可以先修改再返回,还可以返回其他值。
3. 最后,装饰器把包装函数作为调用结果返回,返回后的函数会以原函数的名义将其取代。
装饰器通常与@
开头,放于def
/class
上方,缩紧相同。
先来看看一个装饰器装饰的函数形式:
@float_arg_and_return
def mean(first, second, *rest):
numbers = (first, second) + rest
return sum(number)/len(number)
其实上面的代码可以与下面的代码等价:
def mean(first, second, *rest):
numbers = (first, second) + rest
return sum(number)/len(number)
mean = float_args_and_return(mean)
也就是说装饰器就是一种语法糖(多吃糖长身体- -)。
接下来介绍float_args_and_return
:
def float_args_and_return(function):
def wrapper(*args, **kwargs):
args = [float(arg) for arg in args]
return float(function(*args, **kwargs))
return wrapper
可以看出function
表示传入装饰器的函数,*args, **kwargs
代表传入函数的参数,这么写也是函数的所有参数的意思。
现在我们可以看出,mean
这个函数接收两个或多个任意类型的参数,并将这些参数均转换为float
类型(并未处理异常),然后返回一个这些数的平均值。
但是,上面方法并没有实现完美的包装,因为进过装饰之后的函数的__name__
属性就会变为包装函数的__name__
,而且即便原函数有docstring
也将不存在了,在这里,Python的标准库为我们提供了@functools.wraps
装饰器来解决它,我们可以在装饰器里用它来装饰包装函数以解决问题:
def float_args_and_return(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
args = [float(arg) for arg in args]
return float(function(*args, **kwargs))
return wrapper
那现在如果你想在装饰器中传递参数如何才能做到呢?定义一个能制作装饰器的函数!
带参数的装饰器
比如我们希望制作一个装饰器,函数运行时打印log,并打印该函数作者名字进入log,但是可能一个程序是多人合作的结果,此时我们可以将作者名字通过装饰器参数传入,这是我们最终呈现的形式:
@log('Bing')
def dosomething(*args):
print '这只是个测试而已...'
我们先说明一下编写装饰器工厂(也就是带参数的装饰,等会就知道为何叫做装饰器工厂了)的一般流程:
1. 首先,创建装饰器函数,在该函数内创建包装函数,包装函数的编写方式如前所述。
2. 在包装函数尾部,把调用原函数所得的返回值(也可以修改返回值,或用其他值来替换)返回给上一层。在装饰器函数尾部返回包装函数。
3. 最后,在装饰器工厂函数尾部返回装饰器
那log则如下编写:
def log(author):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print 'run %s, the author is %s' %(func.__name__, author)
return func(*args, **kwargs)
return wrapper
return decorator
创建好包装函数之后,装饰器会将其返回,而log()
函数又会在其末尾将装饰器返回。当Python执行到@log('Bing')
的时候,它会先调用log()
函数,这个函数会返回decorator()
函数(这也是它被称为装饰器工厂的原因),现在再看@
,Python看到@
之后会执行decorator
函数,并把log
后面的那个函数(do_somethind
)传给decorator
,此时就是普通装饰器的流程了。
类装饰器
我们经常会创建很多具有可读写属性的类,这些类一般具有大量重复的getter
和setter
方法。比如,我们需要创建一个Book
类,此时需要四个属性:title, ISBN, price, quantity
,这时按照一般的方法我们可能需要四个@property
修饰器,其代码还都机会一直,此外,还需要四个setter
方法,每个方法都要验证用户传入的参数,而验证价格与验证数量都属于验证范围,也是很相似,这样势必造成不必要的重复代码。此时,我么就需要使用到类装饰器了,如:
@ensure("title", is_non_empty_str)
@ensure("isbn", is_valid_isbn)
@ensure("price", is_in_range(1, 10000))
@ensure("quantity", is_in_range(0, 1000000))
class Book:
def __init__(self, title, isbn, price, quantity):
self.title = title
self.isbn = isbn
self.price = price
self.quantity = quantity
@property
def value(self):
return self.price * self.quantity
def __repr__(self):
return ("Book({0.title!r}, {0.isbn!r}, {0.price!r}, "
"{0.quantity!r})".format(self))
其中ensure
作为类装饰器函数工厂,接收两个参数,一个是属性名,另一个是验证器函数,然后返回一个类装饰器,这个类装饰器会运用在@ensure
之后的类上面。
其实整个装饰流程应该等同于:
ensure("title", is_non_empty_str)(
ensure("isbn", is_valid_isbn)(
ensure("price", is_in_range(1, 10000))(
ensure("quantity", is_in_range(0, 1000000))(class Book:...))))
接下来看看ensure
函数
def ensure(name, validate, doc=None):
"""This class decorator factory needs a property name and a validate
function, and will accept a property docstring
The validate function should return None on success and raise a
ValueError on failure.
"""
def decorator(Class):
privateName = "__" + name
def getter(self):
return getattr(self, privateName)
def setter(self, value):
validate(name, value)
setattr(self, privateName, value)
setattr(Class, name, property(getter, setter, doc=doc))
return Class
return decorator
我么可以看出它将给定的函数名全部设置为"__"+name
类型,然后内部设置了getter
和setter
方法,在setter
方法中还调用了验证函数。
用类装饰器新增属性
我们这里准备将上述的多个装饰器转换为只用一个类装饰器
@do_ensure
class Book:
title = Ensure(is_non_empty_str)
isbn = Ensure(is_valid_isbn)
price = Ensure(is_in_range(1, 10000))
quantity = Ensure(is_in_range(0, 1000000))
def __init__(self, title, isbn, price, quantity):
self.title = title
self.isbn = isbn
self.price = price
self.quantity = quantity
@property
def value(self):
return self.price * self.quantity
上面是改写后的Book
类,我们使用@do_ensure
类装饰器与Ensure
实例与前例相同的功能。每次构造Ensure
对象时,都要传入验证函数,而@do_ensure
类装饰器会用带有验证机制的同名属性来替换相应的Ensure
实例。
class Ensure(object):
def __init__(self, validate, doc=None):
self.validate = validate
self.doc = doc
def do_ensure(Class):
def make_property(name, attribute):
privateName = "__" + name
def getter(self):
return getattr(self, privateName)
def setter(self, value):
attribute.validate(name, value)
setattr(self, privateName, value)
return property(getter, setter, doc=attribute.doc)
for name, attribute in Class.__dict__.items():
if isinstance(attribute, Ensure):
setattr(Class, name, make_property(name, attribute))
return Class
上面这个类装饰器可分为三部分。在第一部分里,我们定义了名为make_property()
的”嵌套函数”。该函数有两个参数,一个是属性名(比如”title”),另一个是Ensure
类型的attribute
,此函数将返回一个属性,该属性会把其值保存在私有的attribute
中(比如title
属性的值就保存在名为__title
的attribute
中)。属性的setter
函数还会调用原来Ensure
实例的验证器。在第二部分里,我们遍历类中的每一个attribute
,并用新的属性来替换原先的Ensure
实例。第三部分会把修改后的类返回。
执行完装饰器后,受装饰的类里原有的Ensure
型attribute
都会被同名且带有验证机制的属性所取代。
用类装饰器实现继承
def mediated(Class):
setattr(Class, 'mediator', None)
def on_change(self):
if self.mediator is not None:
self.mediator.on_change(self)
setattr(Class, 'on_change', on_change)
return Class
@mediated
class Button(object):
def __init__(self, text=''):
super().__init__()
self.enabled = True
self.text = text
def click(self):
if self.enabled:
self.on_change()
其效果等同于:
class Mediated(object):
def __init__(self):
self.mediator = None
def on_change(self):
if self.mediator is not None:
self.mediator.on_change(self)
class Button(Mediated):
pass
最后仰慕般地推荐文章:
Python修饰器的函数式编程
大神讲得详细了很多(虽然文章之间有大神对装饰器和装饰器模式与《Python编程实战》有所出入,但是一个现象各抒己见,学到东西才是真的)。