[Python]闭包的理解和使用

闭包普遍使用在函数式编程语言中,虽然不是很容易理解,可是又不得不理解。html

#闭包是什么?python

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

举个例子数据库

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都支持闭包编程

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

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

def rlt(v):
   def product(num):
        return num ** v
   return product

square = rlt(2)
cube = rlt(3)

print(square(2), cube(2))  # 4, 8

闭包传递了某些变量,对使用者来讲就便捷了不少,下面会讲到闭包的原理cors

总结下: 闭包其实和普通函数的区别: 一、普通函数传递变量,闭包传递函数 二、闭包的封装性更好,调用的参数更少编程语言

#何时用闭包?函数式编程

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中的闭包

相关文章
相关标签/搜索