一、什么是装饰器

器:工具

装饰:为被装饰对象添加新功能

装饰器本身可以是任意可调用的对象,即函数

被装饰的对象也可以是任意可调用的对象,也是函数

目标:写一个函数来为另外一个函数添加新功能

二、为何要用装饰器

开放封闭原则:软件一旦上线就应该对修改封闭,对扩展开放

  对修改封闭:

    1、不能修改功能的源代码

    2、也不能修改功能的调用方式

  对扩展开放:

    可以为原有的功能添加新的功能

装饰器就是要在不修改功能源代码以及调用方式的前提下为原功能添加额外新的功能

三、使用装饰器

# 第一步
import time def index():
print('欢迎来到index页面')
time.sleep(3) start = time.time()
index()
stop = time.time()
print("执行时间是: %s" %(stop - start))
# 如果想多次计算程序运行时间,每次都要写这几步相减操作去计算 #========================================================================================== # 第二步
import time def index():
print('欢迎来到index页面')
time.sleep(3) # 现在增加函数,可以通过函数实现多次计算
def wrapper():
start = time.time()
index()
stop = time.time()
print("执行时间是: %s" %(stop - start)) wrapper()
# 但每次只能计算index, 功能写死了,且修改了程序的执行方式 #========================================================================================== # 第三步
import time def index():
print('欢迎来到index页面')
time.sleep(3) func = index # func = index最原始的内存地址
def wrapper():
start = time.time()
func()
stop = time.time()
print("执行时间是: %s" %(stop - start)) wrapper()
# 可以计算其他函数了, 但还是修改了程序的执行方式 #========================================================================================== # 第四步
def index():
print('欢迎来到index页面')
time.sleep(3) # 加上闭包函数,实现传参
def outter():
func = index # func = index最原始的内存地址
def wrapper():
start = time.time()
func()
stop = time.time()
print("执行时间是: %s" %(stop - start))
return wrapper outter()
# 执行outter(), 拿到wrapper的内存地址, 可以将index作为outter的参数 #========================================================================================== # 第五步 import time def index():
print('欢迎来到index页面')
time.sleep(3) # 将func作为outter的参数,用于实现传入不同的参数,计算不同程序的运行时间
def outter(func): # func = index最原始的内存地址
def wrapper():
print("hhhhh")
start = time.time()
func()
stop = time.time()
print("执行时间是: %s" %(stop - start))
return wrapper # 将index作为outter的参数, 拿到wrapper的内存地址
index = outter(index)
# 赋值给新的index, 加括号执行
index()
# 实现了一个简单装饰器的功能
现在想给 index 一个返回值, 要想得到这个返回值,只需要将 index函数的执行结果赋给一个变量,输出这个变量即可 import time def index():
print('欢迎来到index页面')
time.sleep(3)
return 123 def outter(func):
def wrapper():
start = time.time()
res = func()
stop = time.time()
print("执行时间是: %s" %(stop - start))
return res
return wrapper index = outter(index)
res = index()
print(res)

一个简单的装饰器

但是这样只能输出 index 函数的返回值,功能又写死了,假如其他函数有返回值呢,但你又不知道返回值是什么,于是需要有进一步的操作

import time

def index():
print('欢迎来到index页面')
time.sleep(3)
return 123 def home(name):
print('欢迎 %s 来到home页面' %name)
time.sleep(1) def outter(func):
# 给wrapper加上参数,只用来接收被装饰函数的参数, 原封不动的转接给最原始的函数
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print("执行时间是: %s" %(stop - start))
return res
return wrapper index = outter(index)
home = outter(home)
home('qiuxi')
index()

改进一

每次调用 outter 函数再传参,再将 outter 的执行结果赋给新的变量,显得非常麻烦,于是可以有简化操作

import time

def outter(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print("执行时间是: %s" %(stop - start))
return res
return wrapper # 一执行这行代码, 就会调用outter(),将下方的函数名作为参数传入outter
# 将返回的结果重新赋值给函数名,即 index = outter(index)
@outter
def index():
print('欢迎来到index页面')
time.sleep(3)
return 123 # 这里和上面是相同的操作
# home = outter(home)
@outter
def home(name):
print('欢迎 %s 来到home页面' %name)
time.sleep(1) home('qiuxi')
index()

改进二

因为是计算执行时间的程序,所以我给装饰器换个名字,这一步没什么操作

import time

def timmer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print("执行时间是: %s" %(stop - start))
return res
return wrapper # 一执行这行, 就会调用timmer()
# timmer将返回的结果重新赋值给函数名
# 即 index = timmer(index)
@timmer
def index():
print('欢迎来到index页面')
time.sleep(3)
return 123 # home = timmer(home)
@timmer
def home(name):
print('欢迎 %s 来到home页面' %name)
time.sleep(1) home('qiuxi')
index()

改进三

写函数的时候,我们要为函数添加注释功能,是在定义函数的下方用三引号写出来,以便于代码的阅读

import time

def timmer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print("执行时间是: %s" %(stop - start))
return res
return wrapper # @timmer
def index():
'''这是index功能'''
print('欢迎来到index页面')
time.sleep(3)
return 123 # @timmer
def home(name):
'''这是home功能'''
print('欢迎 %s 来到home页面' %name)
time.sleep(1) # home('qiuxi')
# index() # 现在我将装饰器这行注释掉
# 不用装饰器打印的是函数的注释信息
# 现在的需求是加上装饰器还应该是函数的注释信息
# 但这里加上装饰器打印的却是wrapper的注释信息
# 是因为加上装饰器后,index与home指向的都是wrapper
print(help(index))
print(help(home))

改进四

加了装饰器后,这里通过index看到wrapper的注释信息,但现在还是想看到自己函数的注释信息,可以到wrapper里面把自己的 __doc__ 这个功能赋给 wrapper,还有一个功能是 __name__,这个功能是查看对应的函数名,不加装饰器对应的是自己的函数名,加了装饰器对应的是 wrapper 的函数名,现在需求是加了装饰器也是对应自己的函数名,可以到 wrapper 里面把这个功能赋给wrapper

import time

def timmer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print("执行时间是: %s" %(stop - start))
return res
# 把传入的函数的注释信息赋值给wrapper的注释信息
wrapper.__doc__ = func.__doc__
# 把传入函数的函数名赋给wrapper
wrapper.__name__ = func.__name__
return wrapper @timmer
def index():
'''这是index功能'''
print('欢迎来到index页面')
time.sleep(3)
return 123 @timmer
def home(name):
'''这是home功能'''
print('欢迎 %s 来到home页面' %name)
time.sleep(1) # home('qiuxi')
# index() print(index.__doc__)
print(index.__name__)

改进五

但一个被装饰的函数显然不仅仅只有这两个功能,那每次都要往 wrapper 添加功能会很麻烦,所以Python提供了一个功能

from functools import wraps
import time def timmer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print("执行时间是: %s" %(stop - start))
return res
# 现在想把func下的属性赋值给wrapper
# Python提供了从functools中导入wraps
# 在wrapper的上方加一个装饰器wraps, func传入wraps
# 做的就是把func的一堆属性赋值给wrapper
return wrapper @timmer
def index():
'''这是index功能'''
print('欢迎来到index页面')
time.sleep(3)
return 123 @timmer
def home(name):
'''这是home功能'''
print('欢迎 %s 来到home页面' %name)
time.sleep(1) # 加了wraps后, 功能都是传入的函数的了
print(help(index))
print(index.__name__)

改进六

至此,无参装饰器便是如此

# 无参装饰器的模版

# 这个func非常固定, 就是用来接收被装饰的函数
def outter(func):
# *和**就是用来接收被装饰函数的参数, 原封不动的转接给最原始的函数
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
return res
return wrapper

应用:用装饰器实现一个登录认证功能

import time
def auth(func):
def wrapper(*args, **kwargs):
username = input("请输入用户名: ").strip()
password = input("请输入密码: ").strip()
if username == 'egon' and password == '':
print("登录成功")
res = func(*args, **kwargs)
return res
else:
print("用户名或密码错误")
return wrapper @auth
def index():
'''这是index功能'''
print("欢迎来到index页面")
time.sleep(2)
return 123 @auth
def home(name):
'''这是home功能'''
print("欢迎 %s 来到home页面" %name)
time.sleep(1) index()
home('qiuxi')

登录功能

这个程序用来访问每个功能都需要登录一次,与现实登录情况不符,可以加一个可变类型的全局变量,用来记录登录的状态,如果登录了,就修改它的值,下一次实现不同的功能就进行判断

# 对于不可变类型,可以加一个global,将局部变量变为全局变量
# 但最好不要用gloabl,这对于程序的严谨性并不友好 import time user_info = {'current_user': None} def auth(func):
def wrapper(*args, **kwargs):
if user_info['current_user'] is not None:
res = func(*args, **kwargs)
return res
username = input("请输入用户名: ").strip()
password = input("请输入密码: ").strip()
if username == 'egon' and password == '':
# 记录登录状态
user_info['current_user'] = username print("登录成功")
res = func(*args, **kwargs)
return res
else:
print("用户名或密码错误")
return wrapper @auth
def index():
'''这是index功能'''
print("欢迎来到index页面")
time.sleep(2)
return 123 @auth
def home(name):
'''这是home功能'''
print("欢迎 %s 来到home页面" %name)
time.sleep(1) index()
home('qiuxi')

改进一

现在想从不同的地方取出用户名和密码, 例如文件、数据库、ldap, 只需要加上判断

import time

user_info = {'current_user': None}

def auth(func):
def wrapper(*args, **kwargs):
if user_info['current_user'] is not None:
res = func(*args, **kwargs)
return res
username = input("请输入用户名: ").strip()
password = input("请输入密码: ").strip() # 加上判断, 只需要控制engine就可以让认证功能执行不同的逻辑
# engine是一个变量, 需要传进来, 函数体需要外部传进来一个值, 有两种方案
# 一个是参数形式, 这里的engine是wrapper函数内的, 但是engine无法通过wrapper传进来,
# 因为wrapper接收的参数是原封不动的给func使用, 而func是被装饰的对象, 如果在这里加了engine参数
# 就是强制让所有被装饰的函数新增engine一个参数, 这显然是不合理的
# wrapper函数的外部函数还有一个参数func, 但engine通过这个外部函数的参数传进来也是不行的
# 因为func的功能也很单一, 就是用来接收被装饰对象的, 所以也不能加其他参数 # 还有一种就是闭包函数形式
if engine == 'file':
if username == 'egon' and password == '':
# 记录登录状态
user_info['current_user'] = username print("登录成功")
res = func(*args, **kwargs)
return res
else:
print("用户名或密码错误")
elif engine == 'mysql':
print("基于mysql数据库的认证")
elif engine == 'ldap':
print("基于ldap的认证")
else:
print("无法识别的认证") return wrapper @auth
def index():
'''这是index功能'''
print("欢迎来到index页面")
time.sleep(2)
return 123 @auth
def home(name):
'''这是home功能'''
print("欢迎 %s 来到home页面" % name)
time.sleep(1) index()
home('qiuxi')

改进二

可见上面通过直接传参的方式并不能实现功能,于是只能通过闭包函数形式传入 engine

import time

user_info = {'current_user': None}

def auth2(engine = 'file'):
def auth(func):
def wrapper(*args, **kwargs):
if user_info['current_user'] is not None:
res = func(*args, **kwargs)
return res
username = input("请输入用户名: ").strip()
password = input("请输入密码: ").strip() if engine == 'file':
if username == 'egon' and password == '':
# 记录登录状态
user_info['current_user'] = username print("登录成功")
res = func(*args, **kwargs)
return res
else:
print("用户名或密码错误")
elif engine == 'mysql':
print("基于mysql数据库的认证")
elif engine == 'ldap':
print("基于ldap的认证")
else:
print("无法识别的认证") return wrapper
return auth # 因为装饰器还是要用auth, 所以执行auth2, 传入engine,
# 拿到原来的auth的地址, 赋给新的auth
auth = auth2(engine = 'file') @auth
def index():
'''这是index功能'''
print("欢迎来到index页面")
time.sleep(2)
return 123 @auth
def home(name):
'''这是home功能'''
print("欢迎 %s 来到home页面" % name)
time.sleep(1) index()
home('qiuxi')

改进四

上面代码简化

import time

user_info = {'current_user': None}

def auth2(engine = 'file'):
def auth(func):
def wrapper(*args, **kwargs):
if user_info['current_user'] is not None:
res = func(*args, **kwargs)
return res
username = input("请输入用户名: ").strip()
password = input("请输入密码: ").strip() if engine == 'file':
if username == 'egon' and password == '':
# 记录登录状态
user_info['current_user'] = username print("登录成功")
res = func(*args, **kwargs)
return res
else:
print("用户名或密码错误")
elif engine == 'mysql':
print("基于mysql数据库的认证")
elif engine == 'ldap':
print("基于ldap的认证")
else:
print("无法识别的认证") return wrapper
return auth # auth = auth2(engine = 'file')
# 所以 @auth可以替换成 @auth2(engine = 'file')
# 然后上面的一行 auth = auth2(engine = 'file') 就可以删除了
@auth2(engine = 'file')
def index():
'''这是index功能'''
print("欢迎来到index页面")
time.sleep(2)
return 123 @auth2(engine = 'file')
def home(name):
'''这是home功能'''
print("欢迎 %s 来到home页面" % name)
time.sleep(1) index()
home('qiuxi')

改进六

所以最终实现的程序结果是

import time

user_info = {'current_user': None}

def auth2(engine = 'file'):
def auth(func):
def wrapper(*args, **kwargs):
if user_info['current_user'] is not None:
res = func(*args, **kwargs)
return res
username = input("请输入用户名: ").strip()
password = input("请输入密码: ").strip() if engine == 'file':
print("基于文件的认证")
if username == 'egon' and password == '':
# 记录登录状态
user_info['current_user'] = username print("登录成功")
res = func(*args, **kwargs)
return res
else:
print("用户名或密码错误")
elif engine == 'mysql':
print("基于mysql数据库的认证")
elif engine == 'ldap':
print("基于ldap的认证")
else:
print("无法识别的认证")
return wrapper
return auth # 程序走到这行, 先不管@符号, 一定是去执行auth2
# 得到一个auth内存地址, 然后再@auth
# 而 @auth就是 auth(index), 即执行auth函数, 这里的index是最原始的index的内存地址
# 得到wrapper的内存地址作为返回值, 然后把返回值赋给新的index
@auth2(engine = 'mysql')
def index():
'''这是index功能'''
print("欢迎来到index页面")
time.sleep(2)
return 123 @auth2(engine = 'file')
def home(name):
'''这是home功能'''
print("欢迎 %s 来到home页面" % name)
time.sleep(1) index()
home('qiuxi')

最终版

补充:global 与 nonlocal

global是将局部变量添加global关键字变为全局变量

x = 0
def f1():
x =1
def f2():
x = 2
def f3():
global x
x = 3
# print(x)
f3()
f2() f1()
print(x) # 最终输出结果为3

global

nonlocal只能用于局部变量,找上层中离当前函数最近一层的局部变量

声明了nonlocal的内部函数的变量修改会影响到离当前函数最近一层的局部变量

x = 0
def f1():
x = 1
def f2():
# x = 2
def f3():
nonlocal x
print(x)
f3()
f2() f1() # 程序的执行结果为1

nonlocal

 四、叠加多个装饰器

加载装饰器就是将原函数名换成装饰器最内层的那个函数,在加载完毕后,调用原函数其实就是在调用最内层的函数。

import time

def timmer(func):
def wrapper1(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print("执行时间是: %s" % (stop - start))
return res
return wrapper1 def auth(engine='file'):
def xxx(func):
def wrapper2(*args, **kwargs):
name = input("用户名: ").strip()
pwd = input("密码: ").strip()
if engine == 'file':
print("基于文件的认证")
if name == "qiuxi" and pwd == "":
print("登录成功")
res = func(*args, **kwargs)
return res
elif engine == 'mysql':
print("基于mysql认证")
elif engine == 'ldap':
print("基于ldap的认证")
else:
print("用户识别错误")
return wrapper2
return xxx # @auth(engine='file')
@timmer
def index():
print("欢迎来到index页面")
time.sleep(2) index()

加载顺序就是得到wrapper的过程,执行顺序就是执行wrapper函数的过程

假设只有一个timmer,加载就是@timmer到wrapper1的过程,调用原函数index就是在执行wrapper1函数

当一个被装饰的对象同时叠加多个装饰器的时候,装饰器的加载顺序是自下而上,装饰器最内层的那个函数的执行顺序是自上而下

 加载顺序:

执行顺序即程序的执行顺序,自上而下,逐级细化

装饰器的先后顺序不同,执行程序的时间也不同

Learning-Python【12】:装饰器的更多相关文章

  1. 理解Python中的装饰器//这篇文章将python的装饰器来龙去脉说的很清楚,故转过来存档

    转自:http://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html 这篇文章将python的装饰器来龙去脉说的很清楚,故转过来存档 ...

  2. 如何用python的装饰器定义一个像C++一样的强类型函数

        Python作为一个动态的脚本语言,其函数在定义时是不需要指出参数的类型,也不需要指出函数是否有返回值.本文将介绍如何使用python的装饰器来定义一个像C++那样的强类型函数.接下去,先介绍 ...

  3. Python的装饰器实例用法小结

    这篇文章主要介绍了Python装饰器用法,结合实例形式总结分析了Python常用装饰器的概念.功能.使用方法及相关注意事项 一.装饰器是什么 python的装饰器本质上是一个Python函数,它可以让 ...

  4. Python各式装饰器

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

  5. Python札记 -- 装饰器补充

    本随笔是对Python札记 -- 装饰器的一些补充. 使用装饰器的时候,被装饰函数的一些属性会丢失,比如如下代码: #!/usr/bin/env python def deco(func): def ...

  6. python基础——装饰器

    python基础——装饰器 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数. >>> def now(): ... print('2015-3-25 ...

  7. 【转】详解Python的装饰器

    原文链接:http://python.jobbole.com/86717/ Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里. 为什么需要装饰器 我们假设你的程序实现 ...

  8. 两个实用的Python的装饰器

    两个实用的Python的装饰器 超时函数 这个函数的作用在于可以给任意可能会hang住的函数添加超时功能,这个功能在编写外部API调用 .网络爬虫.数据库查询的时候特别有用 timeout装饰器的代码 ...

  9. python 基础——装饰器

    python 的装饰器,其实用到了以下几个语言特点: 1. 一切皆对象 2. 函数可以嵌套定义 3. 闭包,可以延长变量作用域 4. *args 和 **kwargs 可变参数 第1点,一切皆对象,包 ...

  10. python基础—装饰器

    python基础-装饰器 定义:一个函数,可以接受一个函数作为参数,对该函数进行一些包装,不改变函数的本身. def foo(): return 123 a=foo(); b=foo; print(a ...

随机推荐

  1. mybatis07--关联查询一对多

    案例   查询国家的同时,查询出国家下的省会信息! 01.使用单表的连接查询 创建对应的实体类 和数据库表 /** * *国家的实体类 */ public class Country { privat ...

  2. 【C++ mid-term exerises】

    1. 用掷骰子方式,模拟班级每个学号被随机抽点的概率. (12分) 具体要求如下: (1)设计并实现一个骰子类Dice. ① 数据成员sides表示骰子面数.构造时,指定骰子是6面,8面,还是其它数值 ...

  3. vue-cli创建第一个项目(用git bash解决上下键移动选择问题)

    我电脑是windows:(nodejs已经有了) 1 下载vue-cli cmd 打开命令行,或者是gitbash.最好是用cnpm比较快. 2   创建项目: dos命令,cd 你的希望创建的文件夹 ...

  4. Spring 注解配置(2)——@Autowired

    版权声明:本文为博主原创文章,如需转载请标注转载地址. 博客地址:http://www.cnblogs.com/caoyc/p/5626365.html  @Autowired 注释,它可以对类成员变 ...

  5. jquery 倒计时

    今天让我公司前端大神,李杨哥,给做了一个jquery倒计时功能  很牛逼 看下面的效果图 这个倒计时是需要传值的,看效果代码讲解  百度云盘 ,压缩包永久有效  链接: https://pan.bai ...

  6. js的小知识7

    1.函数都有返回值...... 而方法的本质也是函数,所有也有返回值. Document.getElementById()返回的是获取的标签 getElementByClassName()和getEl ...

  7. tcpdf开发文档(中文翻译版)

    2017年5月3日15:06:15 这个是英文翻译版,我看过作者的文档其实不太友善或者不方便阅读,不如wiki方便 后面补充一些,结构性文档翻译 这是一部官方网站文档,剩余大部分都是开发的时候和网络总 ...

  8. Oracle ROWNUM用法和分页查询总结

    **************************************************************************************************** ...

  9. SparkSQL与Hive on Spark的比较

    简要介绍了SparkSQL与Hive on Spark的区别与联系 一.关于Spark 简介 在Hadoop的整个生态系统中,Spark和MapReduce在同一个层级,即主要解决分布式计算框架的问题 ...

  10. 可持久化Trie

    ---恢复内容开始--- HAOI 2019 DAY1 T1 我爆零了. 爆零的感觉很难受 原因竟然是我从没犯过的错误 审题不清.情绪低迷. 也许 也许 也许就是想让我知道我有多菜吧. 求前k大的区间 ...