函数闭包入门
Posted 千翻娃儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数闭包入门相关的知识,希望对你有一定的参考价值。
函数闭包入门
什么是闭包
闭包是一种特殊的函数,闭包一般由两个函数构成,分别为内函数和外函数,内函数会引用外函数传入的参数,而外函数返回的结果是内函数本身。
比如:
def func_out(a):#外函数
b=10#a,b都是外函数的变量
def func_in(c):#c是内函数的变量
return a+b+c#内函数调用外函数的变量
return func_in #返回内函数本身
>>>m=func_out(1)#此时外函数传入a=1,返回的是m本质上就是func_in函数对象
>>>m(3)#调用func_in()函数,传入func_in函数的参数c=3
14
闭包的条件
- 在一个外函数中定义了一个内函数
- 内函数调用了外函数的参数
- 外函数返回内函数本身
可以看到,闭包一个很大的特点就是内函数在调用外函数的临时变量。在python中,一个函数内部的变量,若没有特别声明为全局变量,都为该函数局部域下的临时变量,当函数执行完,内部的局部变量也会回收释放掉。而上面这个例子的a和b都还没被释放,可以调用,因为外函数尚未执行完,内函数也属于外函数之中。
可以这么理解:
在函数内部可以直接调用全局变量,如:
a=10 #外部全局变量
def func(b):
return a+b
>>>11
因此,在闭包中,外函数的域如果为A,内函数的域为B,那么B是属于A的,或者说A是B的相对全局域,那么内函数中自然可以调用外函数的变量。
几点理解
- 闭包返回的是一个函数对象
def func_out(a):
b=10
def func_in():
return a+b
return func_in
>>>m=func_out(1)
>>>type(m)
<class \'function\'>
>>>m(1)
12
闭包返回的是内函数,可以看到,调用一个闭包,最后调用的时候都是在内函数实现,而外函数更多是进行了部分变量或其它相关信息的保存和初始化。之后每次调用闭包或者内函数时,这些通用的变量就不用再传进去了。这一点很像一个单函数的类,比如:
class A:
def __init__(self,a,b):
self.a=a
self.b=b
def func(self,c):
return self.a+self.b
>>>m=A(1,10)
>>>m.func(1)
11
上述例子中类所实现的功能就和之前的闭包例子一样。因此很多单方法类可以转换为闭包来实现。
闭包的典型应用
闭包很大的一个应用就是用作装饰器。如我们想给某些需要进行计算的函数进行计时,对某些函数的输入参数进行强制类型检验,这类场景通过装饰器就可以非常优美的实现,而非在每个函数下重复复制类似的代码。
计时器
def timer_func(func):
\'\'\'
用于对调用func函数时进行计时
:param func: 函数对象作为外函数的入参
:return: 内函数
\'\'\'
#内函数
def wrapper(*args,**kwargs):#通过*args,**kwargs来传入函数的所有参数
t1=time.time()
r=func(*args,**kwargs)#内函数调用外函数的参数,该参数是一个函数对象
t2=time.time()
cost=t2-t1
print(\'time cost %s\'%cost)
return r#返回了func(*args,**kwargs)的结果
return wrapper
def func(n):
while n>0:
n=n-1
return n
>>>a=timer_func(func)#返回的是内部的wrapper函数对象
>>>a(100000)##10000会就是*args传入func中。
time cost 0.009999752044677734
0
可以看到,整个装饰函数都只是增加了一个计时功能,而需要被计时的函数则该怎么调用怎么调用。所以内部函数一般返回的都是func(args,*kwargs),即被装饰函数的执行结果。这种装饰可以直接通过语法糖@实现:
@timer_func
def func(n):
while n>0:
n=n-1
return n
>>>func(9999999)
time cost 0.7122969627380371
类型检查
在撰写某些函数时,我们时常想指定输入参数的类型。而在python中,并没有像c语言等有类型检查等功能,因为python是一门动态类型语言。一种方式是通过注解的方式“规定”类型,如:
def func(a:int,b:str)->list:
res=[a**2,b]
return res
#这里的限制仅是一种规约和提醒,并不会报错,但在IDE中会进行提示。
>>>print(func(1,\'2\'))
[1, \'2\']
>>>print(func(2,3))#并不会报错
[4, 3]
如果要对输入参数的类型进行强制检查,则可以使用装饰器,否则对每个函数都进行if else等判断来检查类型,代码将非常冗长,不易突出核心代码。
里面会涉及到inspect库中signature相关的操作:
import inspect
def func(a:int,b:str)->list:
res=[a**2,b]
return res
>>>sig=inspect.signature(func)#提取函数的签名,返回一个Signature对象
>>>sig
<Signature (a: int, b: str) -> list>
>>>parameters=sig.parameters#获取参数的有序字典
>>>parameters
mappingproxy(OrderedDict([(\'a\', <Parameter "a: int">), (\'b\', <Parameter "b: str">)]))
>>>arg_names=tuple(parameters.keys())#获取所有的参数名称
>>>arg_names
(\'a\', \'b\')
因此,如果通过构建装饰器对函数的参数进行类型检查,则装饰器内部自动提取函数的各个参数以及期待的类型,然后进行循环检验即可:
def para_check(func):#外函数,传入的参数是待检验函数对象本身
sig=inspect.signature(func)#获取函数参数签名
parameters=sig.parameters#获取参数的有序字典
arg_names=tuple(parameters.keys())#获取参数的名称
def wrapper(*args,**kwargs):#内函数
check_list=[]#待检验的参数对
for i,val in enumerate(args):#检验所有的位置参数
arg_name=arg_names[i]
anno=parameters[arg_name].annotation#该参数期待的类型
check_list.append((arg_name,anno,val))
for arg_name,val in kwargs.items():#检验所有的关键字参数
anno=parameters[arg_name].annotation
check_list.append((arg_name,anno,val))
for check_arg in check_list:#逐个参数检验
if not isinstance(check_arg[2],check_arg[1]):
raise TypeError(\'the input %s expect type %s,but got %s\'%(check_arg[0],check_arg[1],type(check_arg[2])))
return func(*args,**kwargs)
return wrapper
@para_check
def test(x: int, y: int):
return x + y
>>>print(test(1,2))
3
>>>print(test(1,\'3\'))
TypeError: the input y expect type <class \'int\'>,but got <class \'str\'>
以上是关于函数闭包入门的主要内容,如果未能解决你的问题,请参考以下文章