python之装饰器详解

发布日期:2019-07-24

python的装饰器使用是python语言一个非常重要的部分,装饰器是程序设计模式中装饰模式的具体化,python提供了特殊的语法糖可以非常方便的实现装饰模式。

装饰模式简介

什么叫装饰?简单来说就是在已有的对象上添加格外的属性、方法或一段代码实现一个新的功能但是又不改变原来的对象或上下文的结构;这样做的意义在于为了是程序的设计符合开放-封闭原则,即对扩展开放,但是对修改封闭;也就是说一个类或方法被定义完成后就不要再去修改它了,你可以通过继承、装饰、代理等一些模式去扩展它的功能;最终的目的是为了降耦合和保证系统的稳定性。

python装饰模式的简单实现

class Car(object): """ 一个汽车类 """ def __init__(self): self.logo = "奔驰" # 车标 self.oil = 2 # 油耗 self.ornamental = None # 装饰品 # 安装空调 def air_conditioner(self): print("空调真舒服!") def decorator(self, component): """用来额外添加装饰方法的""" self.ornamental = component# 由于汽车的装饰品会发生变化class Cushion(object): """ 坐垫 """ def __init__(self): self.name = "席梦思"class Flower(object): """ 装饰花 """ def __init__(self, name): self.name = nameif __name__ == "__main__": car = Car() cushion = Cushion() flower = Flower("玫瑰") # 汽车添加一个坐垫 car.decorator(cushion) print(car.ornamental) # 添加一个花 car.decorator(flower) print(car.ornamental)

上例中坐垫和花是可以为汽车动态添加的额外的属性,这样的话可以精简Car类的结构,同时可以扩展Car类的功能。

python的装饰器

python装饰器主要针对的是为一段已完成的方法增加额外的需要执行的代码,为了保持原来的方法不变,装饰器装饰后应该返回一个新的方法;实现这种功能需要一个载体,这个载体可以是函数也可以是类,同时python提供了语法糖@来完成装饰功能。

python装饰器实现原理介绍

@decoratordef get_name(): pass

如上所示,当代码初始化加载上下文的时候,先定义get_name函数,decorator一定要在定义get_name函数之前加载到上下文中,解释器遇到@这种语法糖,会将@下的get_name函数作为参数代入decorator中执行decorator并返回新的方法,重新将返回的方法赋值给get_name。

那decorator究竟是什么结构呢?我们可以看装饰器的要求,需要返回一个新的方法,因此可以是闭包结构,也可以是类结构。

使用闭包结构的装饰器

def decorator(func): def new_func(*args,**kwargs): "do something" res = func(*args,**kwargs) "do something" return res return new_func@decoratordef get_name(): pass

什么是闭包?在函数中定义一个新的函数,这个函数用到了外边函数的变量,将这个函数以及用到的一些变量称之为闭包。闭包函数在初始化上下文的时候是不会被定义的,只有在执行的时候才会被定义。

上例所示,get_name函数作为参数传递到decorator函数中执行decorator函数返回new_func,new_func函数的参数就是get_name函数的参数,new_func赋值给get_name。此时get_name方法中添加了额外的代码。

装饰器带参数

如果需要在原来的装饰器上还需要添加额外的参数,那就必须使用双层闭包结构了。

def new_decorator(pas=""): def decorator(func): def new_func(*args,**kwargs): "do something" print(pas) res = func(*args,**kwargs) "do something" return res return new_func return decorator@new_decorator("new prams")def get_name(): pass

如上所示,pas参数会被传递到闭包函数中去,此时加载上下文时,new_decorator由于自带了()形式,会被直接执行,返回内层decorator函数,然后按照正常的方式装饰过程执行,pas参数由于被内层函数引用住,会长久地驻留在内存中而不会被释放。

多层装饰

如果在已被装饰的函数上再添加额外的代码功能,就需要再添加装饰器了,此时的重点就在于装饰器函数的执行顺序了。

def new_decorator(pas=""): def decorator(func): def new_func(*args,**kwargs): "do something" print(pas) res = func(*args,**kwargs) "do something" return res return new_func return decoratordef tow_decorator(func): def new_func(*args, **kwargs): res = func(*args, **kwargs) "do other something" return res return new_func@tow_decorator@new_decorator(pas="hhhhh")def get_name():

装饰器函数的执行顺序不同于我们的习惯,其按就近原则,即如上new_decorator会先执行装饰,将返回的函数赋值给get_name,然后依次往上;因此装饰代码的整体结构为tow_decorator添加的额外代码包裹new_decorator的代码,这在需要考虑代码的执行顺序是很重要。

使用类结构的装饰器

class Decorator(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): """ 添加额外的代码 :param args: :param kwargs: :return: """ print("do something") return self.func(*args, **kwargs)@Decoratordef get_name(): passprint(isinstance(get_name, Decorator))# 结果True

如上,由于在python中,一个对象只要具备了__call__方法,就可以使用xxx()的形式进行调用执行call方法中的代码,利用的这个原理我们就可以实现装饰器的结构;

初始化上下文时,执行Decorator,get_name作为参数对Decorator的实例进行初始化,返回一个Decorator的实例赋值给get_name,此时get_name是一个类的实例对象,当调用get_name时会执行Decorator实例对象的call方法。但这种方式相对于闭包结构来说就有点复杂了,一般不用。

装饰器带参数结构

如果使用类结构需要添加额外的参数怎么办呢?和闭包结构一样,再添加一层。

def three_decorator(pas=""): class Decorator(object): def __init__(self, func): self.func = func self.pas = pas def __call__(self, *args, **kwargs): """ 添加额外的代码 :param args: :param kwargs: :return: """ print("do something") return self.func(*args, **kwargs) return Decorator@three_decorator("hhhhh")def get_name(): pass

使用装饰器的副作用

装饰器的用处不用多说,函数校验、进入日志、函数执行前后处理等多场景都需要用到,它也有一点副作用。

def tow_decorator(func): def new_func(*args, **kwargs): """new_func function""" res = func(*args, **kwargs) print("do other something") return res return new_func@tow_decoratordef get_name(): """get_name function""" print("jjjjjj")if __name__ == "__main__": print(get_name.__doc__)# 结果‘new_func function’

可以看到,由于get_name被装饰后指向的是new_func函数,所以打印的信息不再是get_name函数的说明了,对于调试是不方便的。我们可以使用functools模块的wraps函数消除这个副作用。

def tow_decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): """new_func function""" res = func(*args, **kwargs) print("do other something") return res return new_func@tow_decoratordef get_name(): """get_name function""" print("jjjjjj")if __name__ == "__main__": print(get_name.__doc__)# 结果get_name function

总结

所以一般创建装饰器的标准结构是:

import functoolsdef decorator(func): @functools.wraps(func) def new_func(*args,**kwargs): "do something" res = func(*args,**kwargs) print("do something") return res return new_func 作者:天宇之游 出处:http://www.cnblogs.com/cwp-bg/ 本文版权归作者和博客园共有,欢迎转载、交流,但未经作者同意必须保留此段声明,且在文章明显位置给出原文链接。