美文网首页
[Python]闭包的理解和使用

[Python]闭包的理解和使用

作者: faithfu | 来源:发表于2019-11-05 15:10 被阅读0次

    闭包广泛使用在函数式编程语言中,虽然不是很容易理解,但是又不得不理解。

    闭包是什么?

    在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
    —— 维基百科)

    举个例子

    def sum(a,b):
       return a+b
    
    def sum1(a):
        def add(b):
             return a+b  #a为外部变量
        return add  #返回函数
    
    type(sum(1,2))  #<class 'int'>
    type(sum1(1))   #<class 'function'>
    

    一般支持将函数当做对象使用的编程语言,如Python,JavaScript都支持闭包

    如何理解闭包

    闭包存在的意义是夹带了外部变量,如果没有的话,其实和普通函数没有区别。同一个函数夹带了不同的私货就是不同的闭包,可以理解为对函数的轻量级的封装。

    下面这个例子是计算数字的平方和立方,如果是用普通函数,要么是需要写两个函数,要么需要传两个参数

    def rlt(v):
       def product(num):
            return num ** v
       return product
    
    square = rlt(2)
    cube = rlt(3)
    
    print(square(2), cube(2))  # 4, 8
    

    闭包相当于固定了某些变量,对使用者来说就便捷了很多,下面会讲到闭包的原理

    总结下:
    闭包其实和普通函数的区别:
    1、普通函数传递变量,闭包传递函数
    2、闭包的封装性更好,调用的参数更少

    什么时候用闭包?

    1. 装饰器

    闭包在python中非常常见,但是可能很难意识到闭包的存在。这里不得不提到Python中的装饰器Decorator。
    装饰器顾名思义是装饰作用。在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

    举个例子,
    计算函数运行时间,正常写法

    import  time
    
    def do_sth():
        time.sleep(3)
    
    
    startTime = time.time()
    do_sth()
    endTime = time.time()
    print("do_sth run {} ".format(endTime-startTime))   #do_sth run 3.0005998611450195
    

    如果我们要计算别的函数运行时间,就要重复多次代码,我们把这些重复代码放到装饰器里去,如下面代码

    import  time
    
    def timer(fun):
        def wrapper():
            startTime = time.time()
            fun()
            endTime = time.time()
            print("{} run {}".format(func.__name__, endTime - startTime))
        return wrapper
    
    @timer
    def do_sth():
        time.sleep(3)
    
    timer(do_sth)()  # 一:不加@timer语法糖的调用方式
    do_sth()  #二:加@timer语法糖的调用方式, 和方式一等价
    

    @timer放到do_sth的函数定义前,相当于执行了
    do_sth = timer(do_sth)

    装饰器Pythonic的调用方式完全和普通函数调用方式一样,是不是很方便?

    如果装饰器需要带参数呢?那就需要再加一层,用于接收这些函数。又是一层的闭包。

    import  time
    
    def timer(text):
        def decorator(fun):
            def wrapper():
                startTime = time.time()
                fun()
                endTime = time.time()
                print("{} {} total run time:{}".format(text, fun.__name__, endTime - startTime))
            return wrapper
        return decorator
    
    @timer('excute')
    def do_sth():
        time.sleep(3)
    

    三层嵌套的效果是
    do_sth = timer('excute')(do_sth)
    想一想下面的代码打印结果是什么?

    print(do_sth.__name__)  
    print(timer('excute').__name__)
    print(timer('excute')(do_sth).__name__)
    

    do_sth.__name__的结果不再是do_sth。这里需要把原始函数的__name__等属性赋值到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

    wrapper.__name__ = func.__name__ ? 不用,Python内置的functools.wraps就是做这个的
    另外,之前写的wrapper是不带参数的,只适配不带参数的函数调用,如果是doActive(active, f)则无法使用。所以更新定义:def wrapper(*args, **kw):

    于是,一个完整的不带参数的decorator的写法如下:

    import time
    import functools
    
    def timer(fun):
        @functools.wraps(fun)
        def wrapper(*args, **kw):
            startTime = time.time()
            fun(*args, **kw)
            endTime = time.time()
            print("{} {} total run time:{}".format(fun.__name__, endTime - startTime))
        return wrapper
    
    @timer
    def do_sth():
        time.sleep(3)
    
    print(do_sth.__name__)   #do_sth
    

    试试改写上面的带参数的decorator

    import  time
    import functools
    
    def timer(text):
        @functools.wraps(timer)
        def decorator(fun):
            @functools.wraps(fun)
            def wrapper():
                startTime = time.time()
                fun()
                endTime = time.time()
                print("{} {} total run time:{}".format(text, fun.__name__, endTime - startTime))
            return wrapper
        return decorator
    
    @timer('excute')
    def do_sth():
        time.sleep(3)
    

    这次如下代码的运行结果是?

    print(do_sth.__name__)
    print(timer('excute').__name__)
    print(timer('excute')(do_sth).__name__)
    

    2. 惰性求值

    常用于数据库访问的时候

    # 伪代码示意
    
    class QuerySet(object):
        def __init__(self, sql):
            self.sql = sql
            self.db = Mysql.connect().corsor()  # 伪代码
    
        def __call__(self):
            return db.execute(self.sql)
    
    def query(sql):
        return QuerySet(sql)
    
    result = query("select name from user_app")
    if time > now:
        print result  # 这时才执行数据库访问
    

    上面这个不太恰当的例子展示了通过闭包完成惰性求值的功能,但是上面query返回的结果并不是函数,而是具有函数功能的类。有兴趣的可以去看看Django的queryset的实现,原理类似。

    3.需要对某个函数的参数提前赋值

    Python中已经有了很好的解决访问 functools.parial,但是用闭包也能实现。

    def partial(**outer_kwargs):
        def wrapper(func):
            def inner(*args, **kwargs):
                for k, v in outer_kwargs.items():
                    kwargs[k] = v
                return func(*args, **kwargs)
            return inner
        return wrapper
    
    @partial(age=15)
    def say(name=None, age=None):
        print name, age
    
    say(name="the5fire")
    # 当然用functools比这个简单多了
    # 只需要: functools.partial(say, age=15)(name='the5fire')
    

    python偏函数int2 = functools.partial(int, base=2),可以类比C++的bind1st, bind2nd

    闭包的原理?

    闭包其实也是一种函数,普通函数的__closure__None,闭包里是是一个元组,存放着所有的cell对象,每个cell`对象保存着这个闭包里所有的外部变量。

    def sum(a, b):
        return  a+b
    print(sum.__closure__)  #None
    
    def rlt(v):
       def product(num):
            return num ** v
       return product
    
    square = rlt(2)
    cube = rlt(3)
    
    print(square.__closure__)    #(<cell at 0x0000000001E2F768: int object at 0x000007FEF25E62B0>,)
    for x in square.__closure__:
        print(x.cell_contents)   #2
    
    print(cube.__closure__)
    for x in cube.__closure__: #(<cell at 0x0000000001E2F798: int object at 0x000007FEF25E62D0>,)
        print(x.cell_contents) #3
    

    参考资料:
    廖雪峰 Python装饰器
    Python中的闭包

    相关文章

      网友评论

          本文标题:[Python]闭包的理解和使用

          本文链接:https://www.haomeiwen.com/subject/pjwdbctx.html