定义
一个变量的可见范围叫做这个变量的作用域
python是一个非块级作用域的语言
>>> for i in range(10):
... name = i
...
>>> print(name)
9
>>> print(i)
9
这里变量是在for循环中定义的,但是循环结束之后这些变量仍然可以继续使用。这就是非块级作用域的语言。
全局作用域
>>> x = 1
>>> def inc():
... x += 1
...
>>> inc()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in inc
UnboundLocalError: local variable 'x' referenced before assignment
这里报错的原因是变量x并没有被事先声明。
这就是作用域的关系了,x是在函数的外部被定义的,因此x也被称为全局变量,在函数内部是看不到x的。
函数内部是一个局部的作用域,不能直接使用全局作用域中的变量x。
但是当我们直接print时是可以用的,(不能使用x来进行运算),如下:
>>> x = 1
>>> def fn():
... print(x)
...
>>> fn()
1
这是因为每个程序都由一个全局作用域,而在全局作用域里的局部作用域会随着我们层次的变深出现多个局部作用域。
局部作用域
>>> def fn():
... xx = 1
... print(xx)
...
>>> fn()
1
>>> print(xx)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'xx' is not defined
此时函数内的xx就是局部作用域的xx,此时在局部作用域外print(xx)
就会报错,xx只在fn()这个函数的作用域里可见。
变量的作用域为变量定义同级的作用域,也就是在哪一个级别定义的,在哪一个级别就可见。
上、下级作用域
>>> def fn():
... xx = 1
... print(xx)
... def inner():
... print(xx)
... inner()
...
>>> fn()
1
1
这里打印了两次,说明xx在两层函数内都起到了作用,即**下级作用域可见上级作用域的变量。**但这个也是有限制的。
>>> def fn():
... xx = 1
... print(xx)
... def inner():
... xx = 2
... inner()
... print(xx)
...
>>> fn()
1
1
这里可以发现xx的值并没有发生改变。上级作用域对下级作用域是只读可见的,read-only。而python中,赋值即定义,所有在下级作用域里面,虽然赋值了,却也是重新定义了,一样的xx不一样的id。
>>> def fn():
... xx = 1
... print(xx)
... def inner():
... xx = 2
... print(id(xx))
... inner()
... print(id(xx))
...
>>> fn()
1
140714263146784
140714263146752
全局变量global
全局变量就是一个可以对所有作用域可用的变量
>>> xx = 1
>>> def fn():
... global xx
... xx += 1
...
>>> fn()
>>> xx
2
在上面这段代码中,xx已经被改变了。gloabl关键字可以提升变量作用域为全局变量。
一般在全局作用域里面定义变量,在使用处用gloabl提升。
如果只是单纯的提升变量,那么这只是一个标记,并没有定义变量,还需要在某处定义变量
>>> def fn():
... global zz
... zz = 3
... print(zz)
...
>>> def fn2():
... print(zz)
...
>>> fn()
3
>>> fn2()
3
#此时fn2已经拿到了全局变量zz
>>> def fn2():
... zz += 1
... print(zz)
...
>>> fn2()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in fn2
UnboundLocalError: local variable 'zz' referenced before assignment
#但是,当我们用来进行运算的时候就报错了
gloabl的提升只对本作用域有用,那么我们需要让他在其他局部作用域有用,就需要再标记,如下:
>>> def fn2():
... global zz
... zz += 1
... print(zz)
...
>>> fn2()
4
但是尽量不要用gloabl。
闭包函数
>>> def counter():
... x = 0
... def inc():
... global x
... x += 1
... return x
... return inc
...
>>> f = counter()
>>> f()
2
>>> f()
3
这里实现的是,每次执行一次,然后加一。那当我们不使用gloabl的时候要如何实现这个功能呢?
- 将变量放到一个容器里
>>> def counter():
... c = [0]
... def inc():
... c[0] += 1
... return c[0]
... return inc
...
>>> f = counter()
>>> f()
1
>>> f()
2
解析:里面的inc()是一个下级作用域,所有对c这个列表是可见的c[0] += 1
这一行等价于c[0] = c[0] + 1
改变了c本身的元素,并没有对c重新赋值。
这种形式我们称为闭包,函数已经结束,但是函数内部部分变量的引用还存在。
局部变量nonlocal
>>> def counter():
... x = 0
... def inc():
... nonlocal x
... x += 1
... return x
... return inc
...
>>> f = counter()
>>> f()
1
>>> f()
2
这里是正常工作的,nonlocal关键字用于标记一个变量由他的上级作用域定义,通过nonlocal标记的变量可读可写
默认参数作用域
>>> def fn(x=[]):
... x.append(1)
... print(x)
...
>>> fn()
[1]
>>> fn()
[1, 1]
>>> fn()
[1, 1, 1]
这里在每次执行fn时,并没有初始化这个列表。
在python中,一切皆是对象,函数也是对象,参数时函数对象的属性,所以函数参数的作用域伴随函数整个生命周期。
这里x时fn中定义的,所有x是fn的一个属性,只要fn存在,x就存在。
>>> fn.__defaults__
([1, 1, 1],)
>>> fn()
[1, 1, 1, 1]
>>> fn.__defaults__
([1, 1, 1, 1],)
这里可以发现fn的默认值和x返回的值一样,且会跟着一起改变。这个x变量是保存在__defaults__
属性里面的,会伴随着整个fn的生命周期。
那什么时候销毁呢?
对于定义在全局作用域里面的函数:
- 重新定义
- del关键字删除
- 程序结束退出
对于局部作用域:
- 重新定义
- del
- 上级作用域被销毁
当使用可变类型作为参数默认值时,需要特别注意
>>> def fn(x=0, y=0):
... x = 3
... y = 3
... print(x, y)
...
>>> fn()
3 3
>>> fn.__defaults__
(0, 0)
这里x和y是2个可变类型,在函数里对x、y重新赋值了,就是对x和y重新定义了一遍,对上级变量没有影响。
列表里的append只是追加,并没有赋值,所以可变类型需要特别注意
怎么解决:
- 使用不可变类型作为默认值
- 函数体内不改变默认值
小结
这章的知识有点混乱,没有理解,需要再琢磨一下