python基础(补充):python三大器之装饰器
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:
def calc_sum(*args):
i = 0
for n in args:
i = i + n
return i
但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args):
def sum():
i = 0
for n in args:
i = i + n
return i
return sum
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数:
f = lazy_sum(1, 3, 5, 7, 9)
print(f)
# <function lazy_sum.<locals>.sum at 0x000002C5C32328C8>
调用函数f
时,才真正计算求和的结果:
print(f())
# 25
在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数:
f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
print(f1 == f2)
# False
f1()
和f2()
的调用结果是互不影响的。
闭包
返回的函数在其定义内部引用了局部变量args
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()
才执行。我们来看一个例子:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:
print(f1())
# 9
print(f2())
# 9
print(f3())
# 9
全部都是9
!原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3
,因此最终结果为9
。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
f1, f2, f3 = count()
再看看结果:
print(f1())
# 1
print(f2())
# 4
print(f3())
# 9
缺点是代码较长,可利用lambda函数缩短代码。
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
def now():
print('2021-04-17')
f = now
f()
__name__
属性
函数对象有一个__name__
属性,可以拿到函数的名字:
print(now.__name__) # now
print(f.__name__) # now
装饰器
现在,假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
decorator的本质就是闭包。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2021-04-17')
调用now()
函数,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志:
now()
# call now():
# 2021-04-17
把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2021-04-17')
执行结果如下:
now()
# execute now():
# 2021-04-17
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:
print(now.__name__)
# wrapper
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
import functools
是导入functools
模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
python基础(补充):python三大器之装饰器的更多相关文章
- Python三大器之装饰器
Python三大器之装饰器 开放封闭原则 一个良好的项目必定是遵守了开放封闭原则的,就比如一段好的Python代码必定是遵循PEP8规范一样.那么什么是开放封闭原则?具体表现在那些点? 开放封闭原则的 ...
- Python菜鸟之路:Python基础-逼格提升利器:装饰器Decorator
一.装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等. 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身 ...
- python三大器之装饰器的练习
装饰器 加载顺序从下至上 执行顺序从上至下 ''' 多层装饰器 ''' def deco1(func): #func=deco2 def wrapper1(*args, **kwargs): '''t ...
- python基础之闭包函数和装饰器
补充:全局变量声明及局部变量引用 python引用变量的顺序: 当前作用域局部变量->外层作用域变量->当前模块中的全局变量->python内置变量 global关键字用来在函数或其 ...
- python 基础篇 11 函数进阶----装饰器
11. 前⽅⾼能-装饰器初识本节主要内容:1. 函数名的运⽤, 第⼀类对象2. 闭包3. 装饰器初识 一:函数名的运用: 函数名是一个变量,但他是一个特殊变量,加上括号可以执行函数. ⼆. 闭包什么是 ...
- Python-Day4 Python基础进阶之生成器/迭代器/装饰器/Json & pickle 数据序列化
一.生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面 ...
- Python基础2:反射、装饰器、JSON,接口
一.反射 最近接触到python的反射机制,遂记录下来已巩固.但是,笔者也是粗略的使用了__import__, getattr()函数而已.目前,笔者的理解是,反射可以使用户通过自定义输入来导入响应的 ...
- Python基础(7)闭包函数、装饰器
一.闭包函数 闭包函数:1.函数内部定义函数,成为内部函数, 2.改内部函数包含对外部作用域,而不是对全局作用域名字的引用 那么该内部函数成为闭包函数 #最简单的无参闭包函数 def func1() ...
- python基础编程: 函数示例、装饰器、模块、内置函数
目录: 函数示例 装饰器 模块 内置函数 一.函数示例: 1.为什么使用函数之模块化程序设计: 不使用模块程序设计的缺点: 1.体系结构不清晰,可主读性差: 2.可扩展性差: 3.程序冗长: 2.定义 ...
随机推荐
- Git 学习相关笔记
Git Bash 相关命令学 基础概念 参考: https://www.cnblogs.com/gaoht/p/9087070.html https://www.runoob.com/git/git- ...
- Python 爬虫使用动态切换ip防止封杀
对于爬虫被封禁 ! 爬虫一般来说只要你的ip够多,是不容易被封的. 一些中小网站要封杀你,他的技术成本也是很高的,因为大多数网站没有vps,他们用的是虚拟空间或者是sae,bae这样的paas云. 其 ...
- sql where 1=1 的详细解释
原文来自:https://blog.csdn.net/zc474235918/article/details/50544484 看一下这两个句子: select * from user select ...
- Ctf_show Web[1-8]
CTFshow_web1: F12查看JS即在代码里 CTFshow_web2: 进入网址后页面如下: ①:尝试使用admin登陆,发现页面无回显 ②:尝试单引号闭合,并且添加' or 1=1#,此时 ...
- Java基础语法:标识符
Java所有的组成部分都需要名字. 类名.变量名 以及方法名 都被称为标识符. 一.规则 Ⅰ.首字符 规则:所有的标识符都应该以字母(A-Z 或者 a-z).美元符($).下划线(_)开始. 示例:t ...
- 第46天学习打卡(四大函数式接口 Stream流式计算 ForkJoin 异步回调 JMM Volatile)
小结与扩展 池的最大的大小如何去设置! 了解:IO密集型,CPU密集型:(调优) //1.CPU密集型 几核就是几个线程 可以保持效率最高 //2.IO密集型判断你的程序中十分耗IO的线程,只要大于 ...
- Kubernetes-4.Pods
docker version:19.03.14 kubernetes version:1.19.4 ** 已了解Kubernetes的组成.安装.以及kubectl基本命令使用 本文概述Kuberne ...
- 通过golang小案例,了解golang程序常见机制
目录 代码理解及纠错 1.defer和panic执行先后顺序 2.for循环元素副本问题 3.slice追加元素问题 4.返回值命名问题 5.用new初始化内置类型问题 6.切片append另外一个切 ...
- 阿里的Easyexcel读取Excel文件(最新版本)
本篇文章主要介绍一下使用阿里开源的Easyexcel工具处理读取excel文件,因为之前自己想在网上找一下这个简单的立即上手的博客,发现很多文章的教程都针对比较旧的版本的Easyexcel,没有使 ...
- vscode配置golang开发环境手把手描述篇
1.下载安装Golang https://golang.google.cn/dl/ 一路下一步即可 2.下载安装Vscode https://visualstudio.microsoft.com/zh ...