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. UVA11734_Big Number of Teams will Solve This

    题意很简单,本身也是个水题. 给你两个字符串,如果两个串完全相同,那么输出yes:如果两串只是空格的不同,出去空格后完全相同,那么输出Output Format Error,否则输出Wrong Ans ...

  2. python写BMI指数菜单

    需求: # 1.创建并输出菜单, 菜单是不可变的. 所以使用元组menus = ("1, 录入", "2, 查询", "3, 删除", &q ...

  3. C++解析(14):静态成员变量与静态成员函数

    0.目录 1.静态成员变量 2.静态成员函数 3.小结 1.静态成员变量 成员变量的回顾: 通过对象名能够访问public成员变量 每个对象的成员变量都是专属的 成员变量不能在对象之间共享 新的需求: ...

  4. [AT2172] [agc007_e] Shik and Travel

    题目链接 AtCoder:https://agc007.contest.atcoder.jp/tasks/agc007_e 洛谷:https://www.luogu.org/problemnew/sh ...

  5. 使用SetupDI* API列举系统中的设备

    原文链接地址:https://blog.csdn.net/clteng/article/details/801012?utm_source=blogxgwz8 在Windows系统中提供一组有用的函数 ...

  6. VC 生成后事件 Post-Build Event

    原文链接地址:https://blog.csdn.net/jfkidear/article/details/27313643.https://blog.csdn.net/kevindr/article ...

  7. 第三周——构建一个简单的Linux系统MenuOS

    [洪韶武 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ] 第三周  构建一个 ...

  8. [CEOI2004]锯木厂选址

    link 试题分析 做这种题就应该去先写个暴力代码 #include<iostream> #include<cstring> #include<cstdio> #i ...

  9. 【DP】【CF31E】 TV Game

    传送门 Description 给你一个长度为\(2n\)的数字,每次可以从左侧选一个数字,加入连接到一个数字\(A\)或另一个数字\(B\)后面.\(A,B\)初始为\(0\).\(A\)与\(B\ ...

  10. Idea安装findbugs插件,以及findbugs安装find security bugs插件

    第一:先讲述Idea怎么安装findbugs插件 具体操作如下面的图所示: 然后就可以安装findbugs 第二:findbugs怎么安装find security bugs这个find bugs的插 ...