名字空间和闭包

名字空间 name space

名称空间共3种
locals() 函数内的名称空间,包括局部变量和形参
globals() 全局变量
builtin 内置模块的名字空间
dir(_builtins__) 打印出所有内置方法的名称

作用域的查找顺序

n = 10
def func():
n = 20
print('func:',n)
 
def func2():
n = 30
print('func2',n)
 
def func3():
print('func3:',n)
func3()
func2()
func()

# 得到
func:20
func2:30
func3:30 #为什么这里是30,而不是全局变量10

因为查找顺序LEGB
L:locals
E:enclosing 相邻的
G:globals
B:builtins

locals 是函数内的名字空间,包括局部变量和形参
enclosing 外部嵌套函数的名字空间, 闭包函数外的函数中
globals 全局变量,函数定义所在模块的名字空间
builtins 内置模块的名字空间

当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了
如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了

闭包

def func():
    n = 10
    def func2():
        print('func2',n)
    return func2
func()
f = func()
print(f)   #打印的是func2的内存地址
f()  #得到 func2:10

问题来了,之前f = func()已经执行完毕,作为临时变量的n=10应该已经释放掉了,为什么还能被后来的f()打印出来
因为 def func() 返回的是内部的func2()
我在函数里面套了一个子函数,子函数被函数返回了,
在外面执行子函数的时候,子函数调用了函数的参数,这种情况就叫闭包
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

这里的fs相当于 [f(),f(),f(),...] f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:

>>> f1()
9
>>> f2()
9
>>> f3()
9

执行循序是:
1: fs=[]
2: i=1 ,f()函数记下被调用1次,既[f()]。
3:i=2,f()函数记下被调用2次,既[f(),f()]。
4:i=3,f()函数记下被调用3次,既[f(),f(),f())]。。
5:执行第一个f(),需要变量i,i=3,执行f(i),得到f(i)=9,所以fs=[9,f(),f()]
6: 执行第二个f(),需要变量i,i=3,执行f(i),得到f(i)=9,所以fs=[9,9,f()]
7: 执行第三个f(),需要变量i,i=3,执行f(i),得到f(i)=9,所以fs=[9,9,9]

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

缺点是代码较长,可利用lambda函数缩短代码。
执行循序是:
1:fs = []
2:i=1,"fs.append(f(i))"中注意,fs = [f(i)]!不是fs=[f()],所以不是返回函数,是返回函数值了!所以当前立马执行f()这个函数体,而f()函数体中g被执行(f()没有其他要执行的,g是返回函数最后执行),所以g()=1*1=1,既fs = [f(i)]=[1] .....
练习
利用闭包返回一个计数器函数,每次调用它返回递增整数:

def createCounter():
    ax = 0
    def counter():
        nonlocal ax
        ax += 1
    return ax
return counter
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) 
# 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')

解题思路
1.闭包,那么就要多次嵌套了
2.真正起作用的代码在内层嵌套里面
3.函数返回内层嵌套的对象

def createCounter():
    def counter():
        ax += 1
    return counter

4.ax从哪里来,闭包的好处就是可以读取外层嵌套函数的变量

def createCounter():
    ax = 0
    def counter():
        nonlocal ax
        ax += 1
    return counter

如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了
5.这样还不能循环啊,每运行一次createCounter()就自动加一,需要将上次加得的数给送给下一次

def createCounter():
    ax = 0
    def counter():
        nonlocal ax
        ax += 1
        return ax
    return counter

为什么需要闭包?
最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
闭包作用如下:

  1. 避免使用全局变量,实现数据隐藏和保持,也就是面向对象的特性:封装。
  2. 当成员变量比较少,以及方法只有一个时,比类更简单实现。
  3. 数据和函数一起封装,适应异步或并发运行。

如何构建闭包?
当在一个函数里嵌套定义一个函数时,就会产生一个闭包,因此定义一个闭包需要下面几个条件:

  1. 嵌套函数定义(函数内定义函数)。
  2. 最内层嵌套函数访问函数外的局部变量。
  3. 函数返回内嵌套函数对象。
def lazy_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax
 
f = lazy_sum(1, 2, 4, 5, 7, 8, 9)
print(f)
print(f())
# 得到
36
Traceback (most recent call last):
File "D:/Pythonwork/trytest.py", line 9, in <module>
print(f())
TypeError: 'int' object is not callable

注意两者区别

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
 
f = lazy_sum(1, 2, 4, 5, 7, 8, 9)
print(f)
print(f())
# 得到
<function lazy_sum.<locals>.sum at 0x00000000021EF378>
36

有了闭包操作,f得到的是一个内存地址,f()得到的是数,当你的程序需要这两种情况的参数的时候,需要进行闭包操作

小结
一个函数可以返回一个计算结果,也可以返回一个函数。
返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

猜你喜欢

转载自www.cnblogs.com/welljoy/p/9202734.html