一、闭包函数
什么是闭包:python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有。这样我们可以理解,在函数内创建一个函数的行为是完全合法的,而这种函数就叫内嵌函数。内嵌函数(可以理解为内部函数)可以在外部函数作用域内正常调用,在外部函数作用域外则会报错。如果内嵌函数(可以理解为内部函数)引用了外部函数定义的对象(可以是外层之外,但不是全局变量),那么此时的函数就叫闭包函数。
总之,一句话。如果外部函数的变量,被内部函数所引用,这种方式就是闭包。
比如:实现一个常规的累加函数,这是常规写法:
def func():
num1 = 1
num2 = 2
return num1 + num2
print(func())
3
使用闭包函数的写法:
def func(num1):
def add(num2):
return num1 + num2
return add
a = func(1)
print(a(2))
3
在这里,func()相当于外部函数,add()相当于内部函数,外部函数(func)的变量num1被内部函数(add)引用,这种方式就是闭包。另外,需要注意的是外部函数返回的是内部函数名。写法可以固定为:
def func1():
def func2():
...
return func2 #外部函数返回的是内部函数名
对比以上两种写法,其实看不出闭包有什么优势,反而变得更加繁琐。现在再举个例子,加深对闭包优势的了解。
比如,设计一个计数器:
def counter():
cnt = [0]
def add_one():
cnt[0] += 1
return cnt[0]
return add_one
num1 = counter()
print(num1())
print(num1())
print(num1())
print(num1())
1
2
3
4
其实用常规方法设计的函数也可以实现,但换一种思路,在实际生活中,需求是不断变化的,现在需要一种从10开始的计数器,难道我们又要重新开发一套程序吗?没有更好的办法了吗?现在闭包的优势就来了。
def counter(FIRST=0):
cnt = [FIRST]
def add_one():
cnt[0] += 1
return cnt[0]
return add_one
num1 = counter() #从1开始的计数器
num10 = counter(10)#从10开始的计数器
print(num1())
print(num1())
print(num1())
print(num10())
print(num10())
print(num10())
1
2
3
11
12
13
在这里,我们只需要简单的更改少量的代码counter(10),就可以实现新的需求。
在实际测试工作中,我们会经常测试程序的性能,其中一个主要的指标是看程序的运行时间,在此场景中,闭包函数就会经常被使用,它会大大提高工作的效率。比如:
import time
def cost_time(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print(f'程序运行时间为:{end_time - start_time}')#f字符串格式化在pyhton3.7开始使用
return wraper
@cost_time #装饰器
def my_func():
time.sleep(2)
my_func()
程序运行时间为:2.0004215240478516
这里引用了装饰器,一般闭包函数和装饰器是配套使用的,这会大大提高python程序的效率。下面介绍装饰器的用法:
二、装饰器函数
什么是装饰器:python装饰器(function decorators)就是用来扩展函数功能的一种函数,其目的就是不改变原函数名(或类名)的情况下,给函数增加新的功能。装饰器就是通过闭包函数来给原来函数增加功能。因为调用函数不美观,所以引用了语法糖,也就是在需要添加功能的函数前面加上@即可。
需要注意,@修饰符的用法。
- '@’符号用作函数修饰符,必须出现在函数定义的前一行。不允许和函数定义在同一行。什么意思?还是用上面的例子举例:
@cost_time #'@’符号用作函数修饰符,必须出现在函数定义的前一行,这就是第一行。
def my_func():#这是函数,应该在'@’函数修饰符的下一行。
time.sleep(2)
- '@’符号用作函数修饰符,必须模块或者类定义层内对函数进行修饰,不允许修饰一个类。
- 一个修饰符就是一个函数,它将被修饰的函数作为参数,并返回修饰的同名函数或其他可调用的东西。
@cost_time #装饰器
def my_func():
time.sleep(2)
这里的@,我们称之为语法糖,@cost_time就相当于cost_time(my_func),只不过更加的简洁。
- 带一个参数的装饰器
在之前计算程序运行时间的例子中,my_func()函数没有带参数,如果需要带参数呢?
import time
def cost_time(func):
def wrapper(info):
print("this is decorator")
start_time = time.time()
func(info)
end_time = time.time()
print(f'程序运行时间为:{end_time - start_time}')
return wraper
@cost_time
def my_func(info):
print(info)
time.sleep(1)
my_func("hello world")
this is decorator
hello world
程序运行时间为:1.0005288124084473
- 带多个参数的装饰器
在这里, my_func(info)带了info参数,如果要实现的程序需要多个参数怎么办呢?一般情况下,我们会把*args和**kwargs,,作为装饰器内部函数wrapper()的参数。
def decorator(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
- 带自定义参数的装饰器
装饰器可以接受原函数任意类型和数量的参数,还可以接受自定义的参数,比如我要想控制程序运行的次数。
import time
def repeat(num):
def cost_time(func):
def wrapper(*args,**kwargs):
start_time = time.time()
for i in range(num):
print("this is decorator")
func(*args,**kwargs)
end_time = time.time()
print(f'程序运行时间为:{end_time - start_time}')
return wrapper
return cost_time
@repeat(3)
def my_func(info):
print(info)
time.sleep(1)
my_func("hello world")
print(my_func.__name__)
this is decorator
hello world
this is decorator
hello world
this is decorator
hello world
程序运行时间为:3.006178617477417
wrapper#不再是my_func函数
这里需要注意的是my_func(info)函数被装饰后,元信息发生了改变,print(my_func.name)显示的结果是wrapper函数,不再是原来的my_func函数。为了解决这个问题,可以使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息,其本质也就是将原函数的元信息,拷贝到对应的装饰器函数里。
import time
import functools
def cost_time(func):
@functools.wraps(func)#使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息
def wraper(info):
print("this is decorator")
start_time = time.time()
func(info)
end_time = time.time()
print(f'程序运行时间为:{end_time - start_time}')
return wraper
@cost_time
def my_func(info):
print(info)
time.sleep(1)
my_func("hello world")
print(my_func.__name__)
this is decorator
hello world
程序运行时间为:1.0009491443634033
my_func#元信息保留了
- 装饰器的嵌套
python也支持多个装饰器,比如:
@decorator1
@decorator2
def hello(info):
print(info)
它等同于
decorator1(decorator2(hello))
执行的顺序是从左到右执行,比如:
import functools
def decorator1(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("this is decorator1")
func(*args,**kwargs)
return wrapper
def decorator2(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("this is decorator2")
func(*args,**kwargs)
return wrapper
@decorator1
@decorator2
def hello(info):
print(info)
hello("hello world!")
this is decorator1
this is decorator2
hello world!
先执行的是decorator1,然后再执行decorator2,最后执行hello函数。