Python之路(第十一篇)装饰器
一、什么是装饰器?
装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。
强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式
装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能
二、软件开发的原则
软件开发 的 一个原则:“开放-封闭”原则 开放:对现有功能的扩展开放 封闭:已实现的功能代码块不应该被修改
三、装饰器的应用
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
四、装饰器的知识储备
装饰器 = 高阶函数+函数嵌套+闭包
高阶函数定义:
1、函数接收的参数是一个函数名
2、函数的返回值是一个函数
3、满足上述条件任意一个,都可以称之为高阶函数
五、闭包
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
在python语言中形成闭包的三个条件,缺一不可:1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量3)外部函数必须返回内嵌函数——必须返回那个内部函数
一般情况下,如果一个函数运行结束了,内存中将不会存储任何关于该函数的东西,函数的局部变量都会消失,但是闭包是一个特殊的情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
例子
def outer():
name = "nicholas"
def inner(): # inner()是内函数
print(name) #在内函数中 用到了外函数的临时变量
print("里面的一层", locals())
print("外面的一层", locals())
return inner #外函数的返回值是内函数的引用
a = outer()
a() #执行函数内部的inner()函数
六、装饰器
最简单的装饰器
def test(func): #参数中直接是函数名
def wrapper(): #函数嵌套
print("装饰器")
print("----")
func()
return wrapper #返回值是函数名
def func1():
print("from func1")
func1 = test(func1) #将test()函数的返回值wrapper函数的内存地址重新赋值给func1
func1() #这里执行的func1()其实是在wrapper()执行函数里,没有改变原func1()函数的调用方式,
#但是同样执行了原函数
使用语法糖、对原函数有参数的函数进行装饰,为装饰器函数加上返回值
def test(func): #参数中直接是函数名
def wrapper(*args,**kwargs): #函数参数加入可变参数*args和关键字参数**kwargs
# 有了这两个参数,装饰器就可以用于任意目标函数了。
print("装饰器")
print("----")
res =func(*args,**kwargs) #将函数结果赋值给res,并返回res,获取被装饰函数的返回值
return res
return wrapper #返回值是函数名
@test #这里相当执行了func1 = test(func1)语句,这就是语法糖,写在要装饰的函数上面
def func1(): #这不传入参数
print("from func1")
@test #这里相当于执行了 func2 = test(func2)语句,这就是语法糖
def func2(name,age): #这里传入2个位置参数
print("from func2")
print(name)
print(age)
@test
def func3(name,age,gender): #这里是传入了3个关键字参数
print("from func3")
print(name,age,gender)
@test
def func4(name,age,gender): #传入2个位置参数,1个关键字参数,注意关键字参数应该在位置参数之后
print("from func4")
print(name, age, gender) func1() #这里执行的func1()其实是在wrapper()执行函数里,没有改变原func1()函数的调用方式
print("----func函数之间分割线---")
func2("nick",18)
print("----func函数之间分割线---")
func3(name = "nicholas",age = 18,gender = "male")
print("----func函数之间分割线---")
func4("jack",53,gender="male")
输出结果
装饰器
----
from func1
----func函数之间分割线---
装饰器
----
from func2
nick
18
----func函数之间分割线---
装饰器
----
from func3
nicholas 18 male
----func函数之间分割线---
装饰器
----
from func4
jack 53 male
七、解压序列赋值
需求:现在有一个包含N个元素的元组或者是序列,怎样将它里面的值解压后同时赋值给N个变量
li = [2,7,3,8]
a,b,c,d = li #多个变量同时赋值,注意左边的变量个数要与右边的元素个数相等,如果不相等会报错
print(a,b,c,d)
输出结果
2 7 3 8
例子2
li = [1,2,3,4,5,6,7,8,9,10]
a,*b,c = li #中间的*代表中间所有的,把中间所有的赋值给变量b,开头和结尾的元素分别赋值给a,c
print(a)
print(b)
print(c)
输出结果
1
[2, 3, 4, 5, 6, 7, 8, 9]
10
例子3
li = [1,2,3,4,5,6,7,8,9,10]
a,b,*s,c,d = li #取前2个和后2个,中间的直接赋值给s
print(a)
print(b)
print(s)
print(c)
print(d)
输出结果
1
2
[3, 4, 5, 6, 7, 8]
9
10
不关心的元素,可以用特殊变量(_)获取,不必使用
li = [1,2]
a,_ = li #_下划线代表不关心的元素,可以丢掉的元素,注意左右两边元素数量相等
print(a)
li2 = [1,2,3,4,5]
_,_,b,_,_ = li2
print(b)
八、模拟购物商城登录权限验证的装饰器
简单版,功能函数只是做了简单处理,未写全
实现功能,在每个功能函数执行前验证是否登录成功。
userinfo = [{"name":"nicholas","passwd":"123"},
{"name":"jack","passwd":"456"},
{"name":"charles","passwd":"789"},
{"name":"richard","passwd":"147"},
]
#这里简单直接用列表加字典存储用户信息
user_status = {"username":None,"status":False}
#确定用户的登录状态
def auth_func(func):
def wrapper(*args,**kwargs):
if user_status["username"] and user_status["status"]: #判断用户之前是否登录成功,
# 如果是登录成功继续执行其他功能函数
res = func(*args,**kwargs)
return res #返回功能函数的返回值
user_name = input("请输入您的账户名:").strip() #没有登录成功就要求用户输入账户名密码
user_passwd = input("请输入您的密码:").strip()
for i in userinfo: #for循环遍历确定用户名密码是否正确
if user_name == i["name"] and user_passwd == i["passwd"]:
print("恭喜您,登录成功!%s"%user_name)
user_status["username"] = user_name #改变用户登录的状态以便执行下一功能函数时可以直接执行,
# 不用再次登录
user_status["status"] = True
res = func(*args, **kwargs)
return res
else:
"""这里用for-else语句的一个特性,如果for循环语句中执行了return语句那么就不执行else语句,
如果没有执行到return语句那么就继续执行else语句,这里如果用for循环遍历没有符合if语句的账户名密码,
那就执行不了return语句,即在输入过程中用户输入的账户、密码不正确,这里就继续执行else语句显示
"账户密码不正确"正合适
"""
print("您输入的账户或者密码不正确!")
return wrapper
@auth_func 等价于index = auth_func(index)
def index():
#商城主页
print("欢迎来到购物商城!")
@auth_func
def shopping_cart(name):
#购物车模块
print("%s的购物车有%s,%s"%(name,"物品1","物品2"))
@auth_func
def ord(someth1,someth2):
#订单模块
print("买的物品是%s %s"%(someth1,someth2))
index()
shopping_cart("nicholas")
ord("CRV","基金")
语法糖@auth_func,相当于将@下面的函数作为参数传入auth_func()函数中,并且使原函数名=auth_func(函数名)
进一步升级,为装饰器函数增加参数(不是针对原函数的参数进行处理)
userinfo = [{"name":"nicholas","passwd":"123"},
{"name":"jack","passwd":"456"},
{"name":"charles","passwd":"789"},
{"name":"richard","passwd":"147"},
]
#这里简单直接用列表加字典存储用户信息
user_status = {"username":None,"status":False}
#确定用户的登录状态
def auth(sign):
def auth_func(func):
def wrapper(*args,**kwargs):
if sign == "s1":
# print("模式1")
if user_status["username"] and user_status["status"]: #判断用户之前是否登录成功,
# 如果是登录成功继续执行其他功能函数
res = func(*args,**kwargs)
return res #返回功能函数的返回值
user_name = input("请输入您的账户名:").strip() #没有登录成功就要求用户输入账户名密码
user_passwd = input("请输入您的密码:").strip()
for i in userinfo: #for循环遍历确定用户名密码是否正确
if user_name == i["name"] and user_passwd == i["passwd"]:
print("模式1")
print("恭喜您,登录成功!%s"%user_name)
user_status["username"] = user_name #改变用户登录的状态以便执行下一功能函数时可以直接执行,
# 不用再次登录
user_status["status"] = True
res = func(*args, **kwargs)
return res
else:
"""这里用for-else语句的一个特性,如果for循环语句中执行了return语句那么就不执行else语句,
如果没有执行到return语句那么就继续执行else语句,这里如果用for循环遍历没有符合if语句的账户名密码,
那就执行不了return语句,即在输入过程中用户输入的账户、密码不正确,这里就继续执行else语句显示
"账户密码不正确"正合适
"""
print("您输入的账户或者密码不正确!")
elif sign == "s2":
print("模式2")
#这里写一些逻辑功能模块加再次对之前的功能函数进行封装
else:
print("模式3")
# 这里写一些逻辑功能模块加再次对之前的功能函数进行封装
return wrapper #返回内层装饰器封装的函数
return auth_func #返回外层装饰器封装的函数
@auth(sign="s1") #这里的"s1"是个无意义的参数,可根据具体需求情况修改
# 这里等价于 index = auth("s1")(index),即先执行index = auth("s1")返回auth_func函数的
# 内存地址temp,然后再执行index =temp(index),这里即可返回wrapper函数的内存地址
def index():
#商城主页
print("欢迎来到购物商城!")
@auth(sign="s1") #等价于 shopping_cart = auth("s1")(shopping_cart)
def shopping_cart(name):
#购物车模块
print("%s的购物车有%s,%s"%(name,"物品1","物品2"))
@auth(sign="s1") # ord = auth("s1")(ord)
def ord(someth1,someth2):
#订单模块
print("买的物品是%s %s"%(someth1,someth2))
index()
shopping_cart("nicholas")
ord("CRV","基金")
auth函数实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。
九、装饰器的执行过程
单个装饰器函数的执行过程
例子
def auth(func):
print("1")
def wrapper():
func()
print("a")
print("2")
return wrapper
@auth # func1 = auth(func1)
def func1():
print("b")
@auth # func2 = auth(func2)
def func2():
print("c")
func1()
func2()
输出结果
1
2
1
2
b
a
c
a
分析执行过程:
1、将auth函数加载到内存不执行
2、执行第一个@auth语法糖,即执行func1 = auth(func1),将func1作为参数运行函数auth()函数,打印输出1,
加载wrapper()函数到内存,打印输出2,由于存在闭包,将临时变量func = func1存入 wrapper()函数的执行环境,返回wrapper函数的内存地址,auth函数执行完毕,关闭函数。
3、执行第二个@auth语法糖,即执行func2 = auth(func2),将func2作为参数运行函数auth()函数,打印输出1,
加载wrapper()函数到内存,打印输出2,由于存在闭包,将临时变量func = func2存入 wrapper()函数的执行环境,返回wrapper函数的内存地址,auth函数执行完毕,关闭函数。
4、执行func1()函数,即这里执行的是第一个语法糖@auth返回的wrapper函数,这里包含了一个临时变量
func = func1,即执行func1(),打印输出b,之后执行print("a")语句打印输出a,
5、执行func2()函数,即这里执行的是第二个语法糖@auth返回的wrapper函数,这里包含了一个临时变量
func = func2,即执行func2(),打印输出c,之后执行print("a")语句打印输出a。
多个装饰器函数的执行过程
装饰顺序按靠近函数顺序执行,调用时由外而内,执行顺序和装饰顺序相反,而非按照一般程序的执行顺序。
例子
def auth(x):
print("1")
def wrapper():
x()
print("a")
print("2")
return wrapper
def auth2(y):
print("3")
def wrapper2():
y()
print("b")
print("4")
return wrapper2
@auth
@auth2
def func():
print("c")
func()
输出结果
3
4
1
2
c
b
a
分析:
执行过程
1、加载函数auth()到内存,不执行
2、加载函数auth2()到内存,不执行
3、执行语法糖@auth @auth2,先执行@auth2,这里执行顺序是按靠近函数顺序执行,即先执行靠近函数的@auth2,而非一般的程序执行顺序 。这里等价于func = auth(auth2(func))。执行@auth2,执行auth2()函数,打印输出3,加载wrapper2()函数到内存,打印输出4,由于存在闭包,将临时变量y=func存入wrapper2()函数,返回wrapper2()函数的内存地址,auth2()函数结束。
此时func = wrapper2()函数的内存地址,即func = auth2(func)。
4、执行语法糖@auth,执行auth()函数,打印输出1,加载wrapper()函数到内存,打印输出2,加载wrapper()函数到内存,打印输出2,由于存在闭包,将临时变量x = auth2(func)即(语法糖@auth2返回的内存地址),存入wrapper()函数,返回wrapper()函数的内存地址,auth()函数结束。
此时func = wrapper()函数的内存地址,即func = auth(auth2(func))。
5、执行func()语句,即执行auth(auth2(func))(),首先执行的auth()函数,auth(auth2(func))整体是第一个装饰器返回的wrapper()函数的内存地址,即这里要执行auth()中的wrapper()函数,此时auth()参数x是auth2(func),执行到x()语句,这里其实是要执行auth2(func)(),而auth2(func)是返回的wrapper2()函数的内存地址,于是从第一个装饰器函数中跳转到第二个装饰器函数,即这里要执行wrapper2()函数,这里的y参数是func,即执行真正的func()函数,打印输出c,然后继续执行wrapper2()函数内的语句print("b")打印输出b,打印输出b,wrapper2()函数执行完成,即第一个装饰器函数中的wrapper()函数中的x()语句执行完成。
6、继续执行第一个装饰器函数中的wrapper()函数,执行print("a")语句,打印输出a,程序结束。
Python之路(第十一篇)装饰器的更多相关文章
- Python高级笔记(十一)装饰器【面试】
1. 需求 开发封闭原则:虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被拓展,即: 封闭:已实现的功能代码块 开发:对拓展开发 2. ...
- python之路(7)装饰器
前言 装饰器:为函数添加附属功能,本质为函数 原则:不修改被修饰函数的源代码 不修改被修饰函数的调用方式 装饰器=高阶函数+函数嵌套+闭包 使用场景演示 定义下面函数 def cal(l): res ...
- python之路《八》装饰器
装饰器是个好东西啊 那么装饰器是个什么样的东西呢,他又能做些什么呢? 1.为什么装饰器 当我们一个程序已经构建完成,并且已经发布出去了,但是现在需要增加一个活动,例如淘宝给你发送一个今日优惠,或者开启 ...
- Python之路(第二十一篇) re模块
一.re模块 正则表达式本身是一种小型的.高度专业化的编程语言,正则表达式就是字符串的匹配规则,在多数编程语言里都有相应的支持,python里对应的模块是re,正则表达式模式被编译成一系列的字节码,然 ...
- Python入门篇-装饰器
Python入门篇-装饰器 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.装饰器概述 装饰器(无参) 它是一个函数 函数作为它的形参 返回值也是一个函数 可以使用@functi ...
- Python菜鸟之路:Python基础-逼格提升利器:装饰器Decorator
一.装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等. 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身 ...
- Python开发【第二十一篇】:Web框架之Django【基础】
Python开发[第二十一篇]:Web框架之Django[基础] 猛击这里:http://www.cnblogs.com/wupeiqi/articles/5237704.html Python之 ...
- Python之命名空间、闭包、装饰器
一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...
- Python之路【第九篇】:Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy
Python之路[第九篇]:Python操作 RabbitMQ.Redis.Memcache.SQLAlchemy Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用 ...
随机推荐
- 四层协议给站点设置独享ip
四层协议给站点设置独享ip 初始化为四层节点 设置独占ip 设置独享的产品不用预设置分组 增加站点 创建站点后,在分组解析里会自动创建一个以站点名为名称的分组并且会自动分配一个独享的ip在这个分组里( ...
- WINDOW 2008多人访问设置
gpedit.msc,在组策略中对位于“计算机配置->策略->管理模板->Windows 组件->远程桌面服务->远程桌面会话主机->连接”中,限制连接数量中进行配 ...
- OnContextMenu实现禁止鼠标右键
OnContextMenu事件 定义和使用:oncontextmenu 事件在元素中用户右击鼠标时触发并打开上下文菜单.注意:所有浏览器都支持 oncontextmenu 事件, contextmen ...
- getRequestURI getRequestURL 区别
参考 https://blog.csdn.net/gavid0124/article/details/45390999/ request.getRequestURL() 返回全路径 request.g ...
- poj1308(并查集)
题目链接:http://poj.org/problem;jsessionid=436A34AE4BE856FB2DF9B264DCA9AA4E?id=1308 题意:给定一些边让你判断是否构成数. 思 ...
- 牛客网练习赛43-C(图论)
题目链接:https://ac.nowcoder.com/acm/contest/548/C 题意:有n个知识点,学会每个知识点花T[i],已经学会了其中k个知识点,有m组关系,t1,t2,t3,表示 ...
- hadoop深入简出(二)
1.上传文件 Hadoop fs -put hello.txt / 2.查看上传的文件 hadoop fs -ls / hadoop fs -text /hello.txt 两个命令都可以 3.创建文 ...
- Could not get lock /var/lib/dpkg/lock更新问题
发生于apt-get update或apt update时. 常见手段,先试试: sudo rm /var/lib/dpkg/lock sudo rm /var/lib/apt/lists/lock ...
- PAT L2-011 玩转二叉树(二叉树层序遍历)
给定一棵二叉树的中序遍历和前序遍历,请你先将树做个镜面反转,再输出反转后的层序遍历的序列.所谓镜面反转,是指将所有非叶结点的左右孩子对换.这里假设键值都是互不相等的正整数. 输入格式: 输入第一行给出 ...
- android屏蔽BACK键、HOME键和多任务键
HOME: @Overridepublic void onAttachedToWindow() { System.out.println("Page01 -->onAttachedTo ...