一、什么是装饰器

器:工具

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

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

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

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

二、为何要用装饰器

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

  对修改封闭:

    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. phpcms 自定义方法

    路径:\install_package\phpcms\libs\functions\extention.func.php <?php //输入栏目ID获取父级栏目名称 function catp ...

  2. 点击app分享链接,js判断手机是否安装某款app,有就尝试打开,没有就下载

    html: <h1 class="downlink"> 前往 </h1> js: document.addEventListener('DOMContent ...

  3. Javascript htmldecode

    // HtmlDecode http://lab.msdn.microsoft.com/annotations/htmldecode.js // client side version of the ...

  4. LINUX常用命令 --- 权限篇

    linux常用命令 linux用户权限相关 root 用户    相当于群主    超级用户 sudo命令   相当于群管理员 普通用户    群成员 查看用户id信息      使用linux    ...

  5. F#周报2019年第4期

    新闻 F# 4.6预览 fuget.org现在显示包依赖从属,你曾经想要了解谁在使用你的类库吗?现在你可以知道了! F#被加入Wikipedia的流式接口页面 采访Erik Schierboom Az ...

  6. Tensorflow 的saved_model模块学习

    saved_model模块主要用于TensorFlow Serving.TF Serving是一个将训练好的模型部署至生产环境的系统,主要的优点在于可以保持Server端与API不变的情况下,部署新的 ...

  7. JavaScript面向对象之get和set设置读写属性

    之前我们通过this和prototype申明的属性都是可读写的属性,如果想实现单独控制,就必须使用get和set存取期. 基本方法的 步骤一般包含两个步骤,1,使用var关键字定义一个私有属性作为中间 ...

  8. 点击刷新验证码所需要的onclick函数

    <img src="__APP__/Public/verify" onclick="this.src=this.src+'?'+Math.random()" ...

  9. -webkit-line-clamp、-webkit-box-orient vue 打包部署后不起作用??

    场景分析:实际开发中,文字描述过长,需要两行或三行显示缩略显示: 实现过程: 实现过程遇到的问题:打包到线上后发现并没有-webkit-box-orient属性,导致省略号并没有按预期展示: 解决方法 ...

  10. [js]js中函数传参判断

    1,通过|| function fun(x,y){ x=x||0; y=y||1; alert(x+y); } fun(); 2.通过undefined对比 function fun(x,y){ if ...