@H_403_1@微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io
@H_403_1@当你选择了一种语言,意味着你还选择了一组技术、一个社区。
@H_403_1@目录
@H_403_1@
@H_403_1@本节我们来介绍闭包 与装饰器 。
@H_403_1@闭包与装饰器是函数的高级用法,其实在介绍完Python 函数我们就可以介绍本节的内容,但由于Python中的类 也可以用来实现装饰器,所以我们等到介绍完了Python 类再来统一介绍闭包与装饰器。
@H_403_1@装饰器使用的是闭包的特性,我们先来介绍闭包,再来介绍装饰器。
1,什么是闭包
@H_403_1@Python 的函数内部还允许嵌套函数 ,也就是一个函数中还定义了另一个函数。如下:
def fun_1():
def fun_2():
return 'hello'
s = fun_2()
return s
s = fun_1()
print(s) # 'hello'
@H_403_1@在上面的代码中,我们在函数fun_1 的内部又定义了一个函数fun_2 ,这就是函数嵌套 。
@H_403_1@我们在学习函数的时候,还知道,Python 函数可以作为函数参数 和函数返回值 。
@H_403_1@因此,我们可以将上面代码中的函数fun_2 作为函数fun_1 的返回值,如下:
def fun_1():
def fun_2():
return 'hello'
return fun_2
@H_403_1@此时,函数fun_1 返回了一个函数,我们这样使用fun_1 :
fun = fun_1() # fun 是一个函数
s = fun() # 调用函数 fun
print(s) # s 就是 'hello'
@H_403_1@我们再来改进函数fun_1 ,如下:
def fun_1(s):
s1 = 'hello ' + s
def fun_2():
return s1
return fun_2
@H_403_1@上面的代码中,内部函数fun_2 返回了变量s1 ,而s1 是函数fun_2 的外部变量 ,这种内部函数 能够使用外部变量 ,并且内部函数 作为外部函数 的返回值 ,就是闭包 。
@H_403_1@编写闭包时都有一定的套路,也就是,闭包需要有一个外部函数包含一个内部函数,并且外部函数的返回值是内部函数。
2,用闭包实现一个计数器
@H_403_1@我们来实现一个计数器的功能,先写一个框架,如下:
def counter():
# 定义内部函数
def add_one():
pass
# 返回内部函数
return add_one
@H_403_1@再来实现计数的功能,如下:
def counter():
# 用于计数
l = [0]
# 定义内部函数
def add_one():
l[0] += 1
return l[0] # 返回数字
# 返回内部函数
return add_one
@H_403_1@上面的代码中,我们使用了一个列表l[0] 来记录累加数,在内部函数add_one 中对l[0] 进行累加。
@H_403_1@我们这样使用这个计数器:
c = counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
@H_403_1@我们还可以使这个计数器能够设置累加的初始值 ,就是为counter 函数设置一个参数,如下:
def counter(start):
l = [start]
def add_one():
l[0] += 1
return l[0]
return add_one
@H_403_1@这样我们就可以使用counter 来生成不同的累加器(从不同的初始值开始累加)。我们这样使用该计数器:
c1 = counter(1) # c1 从 1 开始累加
print(c1()) # 2
print(c1()) # 3
print(c1()) # 4
c5 = counter(5) # c5 从 5 开始累加
print(c5()) # 6
print(c5()) # 7
print(c5()) # 8
@H_403_1@c1 从 1 开始累加,c5 从 5 开始累加,两个互不干扰。
3,什么是装饰器
@H_403_1@装饰器 是闭包 的一种进阶应用。装饰器从字面上理解就是用来装饰 ,包装 的。装饰器一般用来在不修改函数内部代码的情况下,为一个函数添加额外的新功能。
@H_403_1@装饰器虽然功能强大,但也不是万能的,它也有自己适用场景:
- 缓存
- 身份认证
- 记录函数运行时间
- 输入的合理性判断
@H_403_1@比如,我们有一个函数,如下:
def hello():
print('hello world.')
@H_403_1@如果我们想计算这个函数的运行时间,最直接的想法就是修改该函数,如下:
import time
def hello():
s = time.time()
print('hello world.')
e = time.time()
print('fun:%s time used:%s' % (hello.__name__. e - s))
# 调用函数
hello()
@H_403_1@其中,time 模块是Python 中的内置模块,用于时间相关计算。
@H_403_1@每个函数都有一个__name__ 属性,其值为函数的名字。不管我们是直接查看一个函数的__name__ 属性,还是将一个函数赋值给一个变量后,再查看这个变量的__name__ 属性,它们的值都是一样的(都是原来函数的名字):
print(hello.__name__) # hello
f = hello # 调用 f() 和 hello() 的效果是一样的
print(f.__name__) # hello
@H_403_1@但是,如果我们要为很多的函数添加这样的功能,要是都使用这种办法,那会相当的麻烦,这时候使用装饰器就非常的合适。
@H_403_1@最简单的装饰器
@H_403_1@装饰器应用的就是闭包的特性,所以编写装饰器的套路与闭包是一样的,就是有一个外部函数和一个内部函数,外部函数的返回值是内部函数。
@H_403_1@我们先编写一个框架:
def timer(func):
def wrapper():
pass
return wrapper
@H_403_1@再来实现计时功能:
import time
def timer(func):
def wrapper():
s = time.time()
ret = func()
e = time.time()
print('fun:%s time used:%s' % (func.__name__,e - s))
return ret
return wrapper
def hello():
print('hello world.')
@H_403_1@该装饰器的名字是timer ,其接受一个函数类型的参数func ,func 就是要修饰的函数。
@H_403_1@func 的函数原型要与内部函数wrapper 的原型一致(这是固定的写法),即函数参数 相同,函数返回值 也相同。
@H_403_1@英文 wrapper 就是装饰 的意思。
@H_403_1@其实timer 就是一个高阶函数 ,其参数是一个函数类型,返回值也是一个函数。我们可以这样使用timer 装饰器:
hello = timer(hello)
hello()
@H_403_1@以上代码中,hello 函数作为参数传递给了timer 装饰器,返回结果用hello 变量接收,最后调用hello() 。这就是装饰器的原本用法。
@H_403_1@只不过,Python 提供了一种语法糖 ,使得装饰器的使用方法更加简单优雅 。如下:
@timer
def hello():
print('hello world.')
hello()
@H_403_1@直接在原函数hello 的上方写一个语法糖@timer ,其实这个作用就相当于hello = timer(hello) 。
@H_403_1@用类实现装饰器
@H_403_1@在上面的代码中,是用函数 (也就是timer 函数)来实现的装饰器,我们也可以用类 来实现装饰器。
@H_403_1@用类 实现装饰器,主要依赖的是__init__ 方法和__call__ 方法。
@H_403_1@我们知道,实现__call__ 方法的类,其对象可以像函数一样被调用。
@H_403_1@用类来实现timer 装饰器,如下:
import time
class timer:
def __init__(self,func):
self.func = func
def __call__(self):
s = time.time()
ret = self.func()
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__,e - s))
return ret
@timer
def hello():
print('hello world.')
print(hello())
@H_403_1@其中,构造方法__init__ 接收一个函数类型的参数func ,然后,__call__ 方法就相当于wrapper 函数。
@H_403_1@用类实现的装饰器的使用方法,与用函数实现的装饰器的使用方法是一样的。
4,被修饰的函数带有参数
@H_403_1@如果hello 函数带有参数,如下:
def hello(s):
print('hello %s.' % s)
@H_403_1@那么装饰器应该像下面这样:
import time
def timer(func):
def wrapper(args):
s = time.time()
ret = func(args)
e = time.time()
print('fun:%s time used:%s' % (func.__name__,e - s))
return ret
return wrapper
@timer
def hello(s):
print('hello %s.' % s)
hello('python')
@H_403_1@timer 函数的参数 依然是要被修饰的函数,wrapper 函数的原型与hello 函数保持一致。
@H_403_1@用类来实现,如下:
import time
class timer:
def __init__(self,func):
self.func = func
def __call__(self,args):
s = time.time()
ret = self.func(args)
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__,e - s))
return ret
@timer
def hello(s):
print('hello %s.' % s)
print(hello('python'))
@H_403_1@不定长参数装饰器
@H_403_1@如果hello 函数的参数是不定长 的,timer 应该是如下这样:
import time
def timer(func):
def wrapper(*args,**kw):
s = time.time()
ret = func(*args,**kw)
e = time.time()
print('fun:%s time used:%s' % (func.__name__,e - s))
return ret
return wrapper
@timer
def hello(s1,s2): # 带有两个参数
print('hello %s %s.' % (s1,s2))
@timer
def hello_java(): # 没有参数
print('hello java.')
hello('python2','python3')
hello_java()
@H_403_1@这样的装饰器timer ,可以修饰带有任意参数的函数。
@H_403_1@用类来实现,如下:
import time
class timer:
def __init__(self,*args,**kw):
s = time.time()
ret = self.func(*args,**kw)
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__,e - s))
return ret
@timer
def hello(s1,'python3')
hello_java()
5,装饰器带有参数
@H_403_1@如果装饰器也需要带有参数,那么则需要在原来的timer 函数的外层再嵌套一层函数Timer ,Timer 也带有参数,如下:
import time
def Timer(flag):
def timer(func):
def wrapper(*args,**kw):
s = time.time()
ret = func(*args,**kw)
e = time.time()
print('flag:%s fun:%s time used:%s' % (flag,func.__name__,e - s))
return ret
return wrapper
return timer
@Timer(1)
def hello(s1,s2))
@Timer(2)
def hello_java(): # 没有参数
print('hello java.')
hello('python2','python3')
hello_java()
@H_403_1@从上面的代码中可以看到,timer 的结构没有改变,只是在wrapper 的内部使用了flag 变量,然后timer 的外层多了一层Timer ,Timer 的返回值是timer ,我们最终使用的装饰器是Timer 。
@H_403_1@我们通过函数.__name__ 来查看函数的__name__ 值:
print(hello.__name__) # wrapper
print(hello_java.__name__) # wrapper
@H_403_1@可以发现hello 和 hello_java 的__name__ 值都是wrapper (即内部函数wrapper 的名字),而不是hello 和 hello_java ,这并不符合我们的需要,因为我们的初衷只是想增加hello 与hello_java 的功能,但并不想改变它们的函数名字。
@H_403_1@我们可以使用functools 模块的wraps 装饰器来修饰wrapper 函数,以解决这个问题,如下:
import time
import functools
def Timer(flag):
def timer(func):
@functools.wraps(func)
def wrapper(*args,s2))
@Timer(2)
def hello_java(): # 没有参数
print('hello java.')
@H_403_1@此时,再查看hello 与 hello_java 的 __name__ 值,分别是hello 和 hello_java 。
7,装饰器可以叠加使用
@H_403_1@装饰器也可以叠加使用,如下:
@decorator1
@decorator2
@decorator3
def func():
...
@H_403_1@上面代码的所用相当于:
decorator1(decorator2(decorator3(func)))
8,一个较通用的装饰器模板
@H_403_1@编写装饰器有一定的套路,根据上文的介绍,我们可以归纳出一个较通用的装饰器模板:
def func_name(func_args):
def decorator(func):
@functools.wraps(func)
def wrapper(*args,**kw):
# 在这里可以使用func_args,*args,**kw
# 逻辑处理
...
ret = func(*args,**kw)
# 逻辑处理
...
return ret
return wrapper
return decorator
# 使用装饰器 func_name
@func_name(func_args)
def func_a(*args,**kw):
pass
@H_403_1@在上面的模板中:
-
func_name 是装饰器的名字,该装饰器可以接收参数 func_args
- 内部函数
decorator 的参数 func ,是一个函数类型的参数,就是将来要修饰的函数
-
func 的参数列表可以是任意的,因为我们使用的是*args,**kw
- 内部函数
wrapper 的原型(即参数与返回值)要与 被修饰的函数func 保持统一
-
@functools.wraps 的作用是保留被装饰的原函数的一些元信息(比如__name__ 属性)
@H_403_1@与装饰器相关的模块有functools 和 wrapt ,可以使用这两个模块来优化完善你写的装饰器,感兴趣的小伙伴可以自己拓展学习。
@H_403_1@(完。)
@H_403_1@推荐阅读:
@H_403_1@Python 简明教程 --- 17,Python 模块与包
@H_403_1@Python 简明教程 --- 18,Python 面向对象
@H_403_1@Python 简明教程 --- 19,Python 类与对象
@H_403_1@Python 简明教程 --- 20,Python 类中的属性与方法
@H_403_1@Python 简明教程 --- 21,Python 继承与多态
@H_403_1@欢迎关注作者公众号,获取更多技术干货。
@H_403_1@ (编辑:北几岛)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|