python 函数 变量和闭包

zhenpy 2019-11-04

首先我们认定,python中定义域查找遵循local->Enclosing->Global->Built-in顺序:

a=1
def func1():
... print(a)
... a=111
... print(a)
...
func1()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in func1
UnboundLocalError: local variable 'a' referenced before assignment

而:

a=1
def fun():
... print(a)
... b=111
... print(b)
...
fun()
1
111
print(b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

我们可以得出结论(打脸):内置函数先在内置函数定义域内(前后)寻找变量;找不到之后再从全局变量中引进,且局部变量无法全局。
如果global:

a=1
def func1():
... global a
... print(a)
... a=111
... print(a)
...
func1()
1
111
a
111

但是不多久后我发现一个问题,代码如下:

a=10
def test():
... a = a + 1
... print(a)
...
test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in test
UnboundLocalError: local variable 'a' referenced before assignment
test(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: test() takes 0 positional arguments but 1 was given
所以这个问题其实可以被拆分为两个问题,一个是arguments的问题,还有一个是variable的问题。
当定义了一个argument的时候只要在括号里把global中的variable代入就是可以运行的,前提就是可一定要定义啊!!!

a=1
def func1(a):
... print(a)
... a=111
... print(a)
...
func1(a)
1
111

也就是说问题根本不在global,而是我有没有把a带进去...(哭泣)
结论就是一切都是我自作多情想多了,自己的bug

a=10
def test(a):
... print(a)
... a=a+1
... print(a)
...
test(a)
10
11

所以回到第一个例子,所谓的“local variable referred before assignment”只是因为我没有把变量在定义的时候放进去。

这是第一件事

第二件:只有模块,类以及函数才会引入新的定义域,其他代码块如(if/else,while)是不会的:

a=0
while a < 4:
... print(a)
... a += 1
...
0
1
2
3
a
4

三: 嵌套和闭包

def out():
... a=7
... def inner():
... nonlocal a
... print(a)
... a=9
... print(a)
... inner()
... print(a)
...
out()
7
9
9

嵌套和nonlocal都超好理解
让我斯巴达的是如下:

def fun2(a):
... print(a)
... def fun3(b):
... print(a,b)
... return fun3 #返回fun3函数结果
...
fun2(1)
1
<function fun2.<locals>.fun3 at 0x000001E2857C24C8>
f=fun2(1)
1
f
<function fun2.<locals>.fun3 at 0x000001E2857A4828>
f(4)
1 4

嗯这就是传说中的闭包,闭包使得函数内部的变量可以一直被保存并且被外部使用(像个自由的包裹一直裹着里面的变量)
为了更直观一点:

def out():
... def inner():
... a=5
... return a
... inner()
... return inner
...
f=out()
f
<function out.<locals>.inner at 0x000001E2857A4678>
f()
5

可见调用的这个定义函数,返回的仍旧是一个函数,而不是一个值。out()不是一个函数运行结果而是一个由返回的inner函数和变量a构成的函数(因为闭包的本质就是一种函数,由局部变量和内部函数构成)。
具体一点说来,在第一个例子中,运行fun2(1)将同时得到print出来的一个a,和一个以fun3为函数,被保留的a和未被赋值的b为变量的函数。【当定义符合闭包条件时,自由变量(此处的f)变成一个闭包类,有函数的效果】。
至于为什么它的地址在变化,我觉得是因为它每次调用都返回了一个新函数(分开储存)。

233333我又看到了一个神奇东西

def count():
... fs=[]
... for i in range(1,4):
... def f():
... return i*i
... fs.append(f)
... return fs
...
f1,f2,f3=count()
f1
<function count.<locals>.f at 0x000001E2857A4438>
f1()
9
f2()
9
f3()
9

此处函数为闭包的原因在于append的那个f,如果我做一个改动

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

它就不是闭包了,count可以正常输出结果。
而在这里,返回的函数是i*i,但是由于返回时i=3,f1,f2,f3都变成了9。

相关推荐