注:python
中和C/C++
一样的性质,均被略过
本博文将简单解释这部分知识,比较简单,但也相对很基础!将使用idle讲解
1.子函数中不能改变全局变量
>>> a = 'globals'
>>> def foo():
a = 'locals'
print(locals())
>>> print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': 'globals', 'foo': <function foo at 0x0000024DC879F1E0>}
>>> foo()
{'a': 'locals'}
>>> print(a)
globals
>>>
在调用foo()
后a
依旧还是globals
原因在于,局部变量和全局变量和C/C++
一样,存放在不同的内存区域,互不影响。
并且,子函数调用变量,先搜索局部变量是否存在再搜索全局变量。
2.函数内嵌与闭包
>>> def out(x):
def inner():
return x*2
print(inner)
return inner
>>> a = out(2)
<function out.<locals>.inner at 0x0000024DC879FD90>
>>> a
<function out.<locals>.inner at 0x0000024DC879FD90>
>>> a1 = out(3)
<function out.<locals>.inner at 0x0000024DC879FE18>
>>> a1
<function out.<locals>.inner at 0x0000024DC879FE18>
>>> a2 = out(4)
<function out.<locals>.inner at 0x0000024DC7CF1E18>
>>> a2
<function out.<locals>.inner at 0x0000024DC7CF1E18>
>>> a()
4
>>> a1()
6
>>>
可见,x
被inner()
当作是全局变量的存在,并且函数名也是可以作为对象直接传递或返回,而不需要函数指针这种东西。
闭包就是函数嵌套产生的新概念,在C/C++
,返回函数名,只代表返回了这个函数所在地址,其环境未知,在return 2*x
会报x
未定义错误。
而python
则不会,上个例子输出a
a1
a2
可以看到地址每个a
内部的inner
地址和a
地址是一样的,同时输出表明function out.<locals>.inner
我输出的是一个function叫inner,结果和分析吻合;而每个a的地址却不一样,可以推断返回的不仅仅是地址,还有别的信息,这就是有关x的信息了,我们把它叫做引用环境。
闭包 == 函数 + 引用环境
python中,如果在一个内嵌函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内嵌函数就被认为是闭包(closure)。
闭包使用典型错误1:
>>> def out(x):
def inner():
x = x*2
return x
return inner
>>> c = out(2)
>>> c()
Traceback (most recent call last):
File "<pyshell#97>", line 1, in <module>
c()
File "<pyshell#95>", line 3, in inner
x = x*2
UnboundLocalError: local variable 'x' referenced before assignment
>>>
这就报错了???明明几乎一样地code出错了?
原因在于:python的规则是只要出现在等号左边的变量都认为是新的变量,即x = x*2
这句,x已经是inner()
内部的局部变量了,但是x
没有先定义,所以报UnboundLocalError
错。
正确:
>>> def out(x):
def inner():
nonlocal x
x = x*2
return x
return inner
>>> c = out(2)
>>> c()
4
>>>
错误2
flist = []
for i in range(3):
def foo(x):
print(x + i)
flist.append(foo)
for f in flist:
f(2)
# results:
4
4
4
>>>
原因在于i
在3次for
之后变成2,而flist
中的foo
并没有给变量实例化,所以只记录了需要加i
,最后在for中都加上了2.
改成:
flist = []
for i in range(3):
def foo(x, y=i):
print(x + y)
flist.append(foo)
for f in flist:
f(2)
# results:
2
3
4
>>>
3.修饰器和@符号
上面讲了闭包的概念后,自然就引出修饰器的定义
>>> def outer(some_func):
def inner(n):
print("before some_func")
ret = some_func() # 1
return ret + n
return inner
>>> def foo():
return 1
>>> foo
<function foo at 0x0000026AC729F6A8>
>>> foo = outer(foo)
>>> foo(1)
before some_func
2
>>> foo
<function outer.<locals>.inner at 0x0000026AC729F7B8>
foo = outer(foo)
这行,将foo再用outer封装后再命名为foo,增加功能但使用方法不变,这就是修饰的概念;
可以看见,前后的foo地址发生变化(显然)
上例自己实现了一个非常简单的修饰器,python中提供了语法糖@
符号来供程序猿使用,不需要写foo = outer(foo)
这条语句。可能会使得意义更加明确吧~
>>> def outer(some_func):
def inner(n):
print("before some_func")
ret = some_func() # 1
return ret + n
return inner
>>> @outer
def foo():
return 1
>>> foo(1)
before some_func
2