1、四种作用域及其生命周期:
(1)local(本地局部作用域),即函数中定义的临时变量,当函数结束时,变量的生命周期结束。
(2)enclosed(闭包,嵌套的父级函数的局部作用域),即闭包外层函数的局部变量,外层函数结束,变量的生命周期结束。
(3)global(全局变量),即模块级别定义的变量,模块销毁,变量的生命周期才会结束。
(4)bulit-in(内置函数)是python解释器,虚拟机内置的变量。
搜索变量的顺序是:(1)->(2)->(3)->(4)
2、global关键字的使用
(1)未使用关键字global的情况下,全局变量在局部作用域里默认是只读的(无法修改),如果为其赋值一个新的值,python认为是在当前的局部作用域里创建一个新的“同名”变量
x = 10
def f1():
x = 20
print(x)
f1()
print(x)
"""
Output:
20
10
"""
(2)在确认使用全局变量的情况下,但是又没有关键字的情况下,如果试图修改全局变量,会报UnboundLocalError
的错误!!!
x = 10
def f1():
print(x) # 确认使用外部作用域
x = 2
#UnboundLocalError: local variable 'x' referenced before assignment
(3)当局部作用域想修改全局变量时,使用关键字global即可
x = 10
def f1():
global x
x = 20
print(x)
f1()
print(x)
"""
Output:
20
20
"""
(4)使用global声明,不能放在使用变量之后,会报错IndentationError
x = 10
def f1():
x = 20
global x # IndentationError: unindent does not match any outer indentation level
print x
f1()
print x
(4)此时你会发现,global只能用作全局作用域的变量,非全局作用域的外层变量好像无效!!!
def f1():
x = 10
def f2():
global x # 如果用global,你会发现修改x失败
x = 20
print(x)
f2()
print(x)
f1()
"""
Output:
20
10
"""
此时怎么办呢?如果你使用的python2
,那么可以使用下面这个方法,把非全局作用域的外层变量变成全局作用域变量…(有点傻但是容易理解)
def f1():
global x # 这个地方将x声明为全局作用域的变量,他的声明周期也就变了。后续使用global声明也能影响到了
x = 10
def f2():
global x
x = 20
print(x)
f2()
print(x)
f1()
"""
Output:
20
20
"""
如果你是用的是python3
,那就简单了!直接使用nonlocal
关键字!!!
def f1():
x = 10
def f2():
#global x # 如果用global,你会发现修改x失败
nonlocal x
x = 20
print(x)
f2()
print(x)
f1()
"""
Output:
20
20
"""
(5)在python中只有模块(module),类(class)以及函数(def, lambda)才会引入新的作用域,其他的模块(if, try, for)不会引入新的作用域
if True:
x = 1
print x
"""
Output:
1
"""
(6)虽然if模块不会引入作用域,但是依旧要注意其逻辑。未执行的代码,变量不存在
if False:
x = 1
print x # ERROR
3、踩坑的地方
(1)Enclosed,闭包踩坑
fs = []
for x in range(3):
print(x)
fs.append(lambda :x)
print("------")
for f in fs:
print(f())
"""
Output:
0
1
2
------
2
2
2
"""
为什么会有这个坑?
因为闭包的对象都是引用而非临时变量。
在上面代码中,添加进list中的x可以简单理解成,x是指向数值1,2,3的一个指针~
随着for循环的进行,x的指向一直在改变,打印list中的值也就改变了。
如何解决这个问题???
重新定义一个函数,强行让其传递一个不会一直改变指向的指针(新的临时变量)。
fs = []
def gen(x):
return lambda :x
for x in range(3):
fs.append(gen(x))
for f in fs:
print(f())
(2)Function arguments,函数形参
def func(data = []):
data.append(1)
return data
print(func())
print(func())
print(func())
"""
Output:
[1]
[1, 1]
[1, 1, 1]
"""
为什么会这样呢?
试着查看一下每次返回的列表的 ID,发现都一样。
def func(data = []):
data.append(1)
return data
"""
Output:
1870100386312
1870100386312
1870100386312
"""
那原因就很简单了,func函数在不同的函数调用中一直在使用同一个列表对象。
为什么会一直使用同一个列表对象呢?
答案是:默认参数语句,总是在 def
关键字定义函数的时候被求值,且仅执行这一次!!!
仅执行一次可以查阅 Python 语言参考(The Python Language Reference) 的相关章节:
https://docs.python.org/zh-cn/3.7/reference/compound_stmts.html#function-definitions
【默认形参值会在执行函数定义时按从左至右的顺序被求值。这意味着当函数被定义时将对表达式求值一次,相同的“预计算”值将在每次调用时被使用。这一点在默认形参为可变对象,例如列表或字典的时候尤其需要重点理解:如果函数修改了该对象(例如向列表添加了一项),则实际上默认值也会被修改。】
绕过此问题的一个方法是使用 None
作为默认值,并在函数体中显式地对其进行测试,例如:
def func(data = None):
if data is None:
data = []
data.append(1)
return data
print(func())
print(func())
print(func())
"""
Output:
[1]
[1]
[1]
"""