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 = name if __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装饰器实现原理介绍

@decorator
def 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 @decorator
def 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 decorator def 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) @Decorator
def get_name():
pass print(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_decorator
def get_name():
"""get_name function"""
print('jjjjjj')
if __name__ == '__main__':
import inspect
print(inspect.signature(get_name))
x = inspect.getsource(get_name)
print(x)
print(get_name.__doc__) # 结果
(*args, **kwargs) # 函数签名
def new_func(*args, **kwargs): # 函数源码
"""new_func function"""
res = func(*args, **kwargs)
print("do other something")
return res
‘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_decorator
def get_name():
"""get_name function"""
print('jjjjjj')
if __name__ == '__main__':
print(get_name.__doc__) # 结果
()
@tow_decorator
def get_name():
"""get_name function"""
print('jjjjjj')
get_name function

创建装饰器的一般方式

  • 闭包结构方式

根据我们上面的分析,标准的装饰器结构:

import functools
def decorator(func):
@functools.wraps(func)
def new_func(*args,**kwargs):
"do something"
res = func(*args,**kwargs)
print("do something")
return res return new_func
  • 使用decorator.py模块的decorator和decorate

闭包结构的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。

from decorator import decorator, decorate, contextmanager
def wrapper(func, *args, **kwargs):
"""装饰函数的内容"""
res = func(*args, **kwargs)
print("do other something")
return res def one_decorator(func):
"""进行装饰"""
return decorate(func, wrapper) @one_decorator
def get_name(pas='fff'):
"""get_name function"""
print('jjjjjj') if __name__ == '__main__':
import inspect
print(inspect.signature(get_name))
x = inspect.getsource(get_name)
print(x)
print(get_name.__doc__)
get_name()

将新函数单独写出来,使得原来的闭包结构变成了两个函数,代码可读性变好了,可以发现decorate帮我们处理了装饰器的副作用,但是一个装饰器需要定义两个函数有点麻烦,要更优雅使用decorator。

from decorator import decorator, decorate, contextmanager

@decorator
def five_decorator(func, *args, **kwargs):
res = func(*args, **kwargs)
print("do other something")
return res @five_decorator
def get_name(pas='fff'):
"""get_name function"""
print('jjjjjj') if __name__ == '__main__':
import inspect
print(inspect.signature(get_name))
x = inspect.getsource(get_name)
print(x)
print(get_name.__doc__)
get_name()

现在装饰器变成定义一个函数的模样了,简单又直观,同时也处理了副作用,是一种理想的装饰器创建方式。

  • 使用wrapt模块的decorator
import wrapt
@wrapt.decorator
def six_decorator(func, instance, args, kwargs):
res = func(*args, **kwargs)
print(instance)
print("do other something")
return res @six_decorator
def get_name(pas='fff'):
"""get_name function"""
print('jjjjjj') if __name__ == '__main__':
import inspect
print(inspect.signature(get_name))
x = inspect.getsource(get_name)
print(x)
print(get_name.__doc__)
get_name()
  • 说明:使用wrapt模块的decorator也可以很直观地实现装饰器,但是该装饰器的参数func, instance, args, kwargs是固定的;args, kwargs参数前面没有*号,instance在装饰器装饰类方法的时候可以得到该类的实例对象;func是被装饰的函数。

总结

  1. python的为了实现装饰模式,特别开发了@语法糖,通过闭包的方式实现装饰;

  2. 实际的项目中常用的装饰方法为自定义闭包或使用内置的decorator模块。

  • 作者:天宇之游
  • 出处:http://www.cnblogs.com/cwp-bg/
  • 本文版权归作者和博客园共有,欢迎转载、交流,但未经作者同意必须保留此段声明,且在文章明显位置给出原文链接。

python设计模式之装饰器详解(三)的更多相关文章

  1. Python操作redis字符串(String)详解 (三)

    # -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1.SET 命令用于设置 ...

  2. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

  3. python 设计模式之装饰器模式 Decorator Pattern

    #写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...

  4. python之装饰器详解

    这几天翻看python语法,看到装饰器这里着实卡了一阵,最初认为也就是个函数指针的用法,但仔细研究后发现,不止这么简单. 首先很多资料将装饰器定义为AOP的范畴,也就是Aspect Oriented ...

  5. Python—装饰器详解

    装饰器:(语法糖) 本质是函数,它是赋予函数新功能,但是不改变函数的源代码及调用方式   原则: 1.不能修改被装饰函数的源代码 2.不能修改被装饰函数的调用方式 3.函数的返回值也不变 这两点简而言 ...

  6. Python中的各种装饰器详解

    Python装饰器,分两部分,一是装饰器本身的定义,一是被装饰器对象的定义. 一.函数式装饰器:装饰器本身是一个函数. 1.装饰函数:被装饰对象是一个函数 [1]装饰器无参数: a.被装饰对象无参数: ...

  7. python装饰器1:函数装饰器详解

    装饰器1:函数装饰器 装饰器2:类装饰器 装饰器3:进阶 先混个眼熟 谁可以作为装饰器(可以将谁编写成装饰器): 函数 方法 实现了__call__的可调用类 装饰器可以去装饰谁(谁可以被装饰): 函 ...

  8. Python全栈开发之8、装饰器详解

    一文让你彻底明白Python装饰器原理,从此面试工作再也不怕了.转载请注明出处http://www.cnblogs.com/Wxtrkbc/p/5486253.html 一.装饰器 装饰器可以使函数执 ...

  9. python函数装饰器详解

    python装饰器(fuctional decorators)简单来说就是修改其他函数的函数. 这样的函数需要满足两个个条件: 1.不能修改原函数的源代码 2.不能改变原函数的调用方式 需要达到的效果 ...

随机推荐

  1. cdq分治学习

    看了stdcall大佬的博客 传送门: http://www.cnblogs.com/mlystdcall/p/6219421.html 感觉cdq分治似乎很多时候都要用到归并的思想

  2. QT 主窗口和子窗口相互切换示例

    QT 主窗口和子窗口相互切换示例 文件列表: SubWidget.h #ifndef SUBWIDGET_H #define SUBWIDGET_H #include <QtWidgets/QW ...

  3. 51nod1238 最小公倍数之和 V3 莫比乌斯函数 杜教筛

    题意:求\(\sum_{i = 1}^{n}\sum_{j = 1}^{n}lcm(i, j)\). 题解:虽然网上很多题解说用mu卡不过去,,,不过试了一下貌似时间还挺充足的,..也许有时间用phi ...

  4. 【洛谷3674】小清新人渣的本愿(莫队,bitset)

    [洛谷3674]小清新人渣的本愿(莫队,bitset) 题面 洛谷,自己去看去,太长了 题解 很显然的莫队. 但是怎么查询那几个询问. 对于询问乘积,显然可以暴力枚举因数(反正加起来也是\(O(n\s ...

  5. docker基础学习

    docker的定义: Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机 ...

  6. 解题:NOIP 2018 赛道修建

    题面 几乎把我送退役的一道题,留在这里做个纪念. 考场看出来是原题结果为了求稳强行花了一个小时写了80pts暴力,然后挂了55pts(真·暴力写挂),结果今天花了不到半个小时连想带写一遍95pts(T ...

  7. 【agc016D】XOR Replace

    Portal --> agc016D Description ​ 一个序列,一次操作将某个位置变成整个序列的异或和,现在给定一个目标序列,问最少几步可以得到目标序列 ​ Solution ​ 翀 ...

  8. 【bzoj1502】月下柠檬树

    Portal -->bzoj1502 Solution 额其实说实在这题我一开始卡在了..这个阴影长啥样上QwQ 首先因为是平行光线然后投影到了一个水平面上所以这个投影一定是..若干个圆再加上这 ...

  9. 图像处理之Canny边缘检测

    http://blog.csdn.net/jia20003/article/details/41173767 图像处理之Canny 边缘检测 一:历史 Canny边缘检测算法是1986年有John F ...

  10. 链接错误 multiply defined (by misc_1.o and misc.o).

    http://www.stmcu.org/module/forum/thread-286128-1-1.html *** Using Compiler 'V5.06 (build 20)', fold ...