目录
运行环境:python3.6
一 、闭包
闭包的概念:
-
闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境 。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用,例如下面的a),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,它的自由变量在创建实例时被确定, 可以保持持久性,以后使用不会再受上下文的干扰。
闭包的作用:
- 函数的局部变量是不能保存的, 但闭包内被引用的自由变量和函数一同存在
- 闭包在运行时可以有多个实例, 不同的引用环境和相同的环境结合,可以产生不同的实例
def func(a): # 变量a和函数一同存在
def add(b):
print('a: ', a, 'b: ', b)
return a + b
return add
demo = func(1)
print(demo(2))
print(demo(3))
>>>
a: 1 b: 2
3
a: 1 b: 3
4
从例子中不难发现,a可以作为一个函数中的常量,与函数一同存在,但使用闭包有什么好处呢?请往下面看
二、闭包的应用 -- 累加计算
我现在想使用两个独立的累加计算,累加的数值不一定一样,而且是独立的。
- a_count 和 b_count 可以代表两个实例,每次累加都是在上一次的基础上(在i上累加),而且两个实例的开始变量都是从 i = 0开始,如果是同一个全局变量,那应该不能实现。
- 这里涉及到nonlocal函数, 它可以改变外部函数中的变量,如果不声明nonlocal i, 那么i的值是不能改变的
def new_counter():
i = 0
def count(p):
nonlocal i
i += p
return i
return count
a_count = new_counter()
print(a_count(1))
print(a_count(1))
b_count = new_counter() # 不同的实例,自由变量互不影响
print(b_count(5))
>>>
1
2
5
三、闭包的应用 -- 类装饰器
现在我们来对闭包进行传参,选用函数作为参数,请仔细看下面的注释,最后一步调用有执行步骤。
def get_age(func):
print('enter get_age')
def new_func1(*args, **kw):
print('my age is xxx')
print(func.__name__)
return func(*args, **kw)
return new_func1
def get_name(func):
print('enter get_name')
def new_func2(*args, **kw):
print('my name is lhf')
print(func.__name__)
return func(*args, **kw)
return new_func2
def get_info(work_years):
print('work_years: ', work_years + 1)
return 'success'
new_func2 = get_name(get_info) # return new_func2
new_func1 = get_age(new_func2) # return new_func1
new_func1(1) # 调用 new_func1 -> return func(*args, **kw): 调用 new_func2 -> return func(*args, **kw) : 调用get_info
>>>
enter get_name
enter get_age
my age is xxx
new_func2
my name is lhf
get_info
work_years: 2
四、装饰器
装饰器其实是运用了闭包的方法来实现的,我把上面的部分代码替换掉,结果一样。
def get_info(work_years):
print('work_years: ', work_years + 1)
return 'success'
new_func2 = get_name(get_info)
new_func1 = get_age(new_func2)
new_func1(1)
'''换成'''
@get_age # new_func2 = get_name(get_info)
@get_name # new_func1 = get_age(new_func2)
def get_info(work_years):
print('work_years: ', work_years + 1)
return 'success'
get_info(1)
五、@functools.wraps(func)
上面大家注意到,执行print(func.__name__)时,输出的函数名不是同一个,本来只是想调用get_info, 但是却返回了一个新函数,现在想返回包含原函数属性的新函数(比如原函数的名称),这个要求不算过分吧,哈哈!所以@functools.wraps(func)替我们实现了。
def get_age(func):
print('enter get_age')
@functools.wraps(func)
def new_func1(*args, **kw):
print('my age is xxx')
print(func.__name__)
return func(*args, **kw)
return new_func1
-
为了保证被装饰器装饰后的函数还拥有原来的属性,wraps返回一个partial对象
- wraps源码,参数解析
wrapped:指被装饰的原函数,可以认为是上述代码中的func
assigned: 要被重新赋值的属性列表,默认 WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
updated: 要被合并的属性列表
update_wrapper函数: 把原函数wrapped属性复制给一个新的函数wrapper,返回新函数
def update_wrapper(wrapper, wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
-
partial基于一个函数创建一个可调用对象,可用下面这个例子理解partial
经过partial包装之后,参数a的值被固定,为1,返回一个新的add对象(相当于闭包函数):new_add(是一个可调用对象,而非函数),new_add具有add的功能
import functools
def add(a, b):
print(a + b)
new_add = functools.partial(add, 1)
new_add(4)
>>>
5
欢迎大家评论!