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. yarn add & yarn global add

    yarn global add & add -D https://yarnpkg.com/zh-Hans/docs/cli/add#toc-commands $ yarn global add ...

  2. 第136天:Web前端面试题总结(理论)

    Web前端面试题总结 HTML+CSS理论知识 1.讲讲输入完网址按下回车,到看到网页这个过程中发生了什么 a. 域名解析 b. 发起TCP的3次握手 c. 建立TCP连接后发起http请求 d. 服 ...

  3. ZOJ3591_Nim

    题目的意思是给你n个ai,有多少种不同的连续段使得用该段数中所有的数字玩Nim游戏的先手必胜. 首先根据博弈论的知识,我们知道,要使先手必胜,那么只要保证所有的数的异或值不为0就可以了. 这个题目,给 ...

  4. python的N个小功能(找到符合要求的图片,重命名,改格式,缩放,进行随机分配)

    ########################################################################## 循环读取该目录下所有子目录和子文件 ####### ...

  5. 洛谷 P4116 Qtree3

    Qtree系列第三题 我是题面 读完题大概不难判断是一道树剖的题 这道题的关键是记录两种状态,以及黑点的序号(不是编号) 线段树啊当然 定义两个变量v,f,v表示距离根节点最近的黑点,默认-1,f则表 ...

  6. 大坑!有网,电脑qq登不上去!!

    手机qq --> 设置 --> 账号设备安全 -->  允许手机电脑同步在线 或是其他设置干扰导致

  7. SCWS中文分词,demo演示

    上文已经讲了关于SCSW中文分词的安装配置,本节进入demo演示: <?php header('Content-Type:text/html;charset=UTF-8'); echo '< ...

  8. (转)Ubuntu 17.04_64上搭建巡风扫描系统(资产信息漏洞扫描内网神器)

    巡风简介 巡风是一款适用于企业内网的漏洞快速应急.巡航扫描系统,通过搜索功能可清晰的了解内部网络资产分布情况,并且可指定漏洞插件对搜索结果进行快速漏洞检测并输出结果报表.其主体分为两部分:网络资产识别 ...

  9. Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序

    Linux内核分析第七周学习笔记--Linux内核如何装载和启动一个可执行程序 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study. ...

  10. 70路小报:用PV和UV作为网站衡量指标已经过时

    方法]投资人呼吁:PV和UV不应该再作为产品衡量指标 风险投资机构Andreessen Horowitz近日一直反对再用传统的网站衡量指标去评价互联网产品,比如PV和UV,甚至包括应用的下载量. 他们 ...