python 闭包、装饰器 - 举个栗子吧

目录

一 、闭包

二、闭包的应用 -- 累加计算

三、闭包的应用 -- 类装饰器

四、装饰器

五、@functools.wraps(func)

运行环境: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

欢迎大家评论!

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值