变量从作用域分类
作用范围从小到大为,小作用域的可以调用大作用域的内容。
- 局部 Local
- 闭包 Enclosing
- 全局 Global
- 内建 Build-in
局部变量
局部变量是定义在函数中的,因此其作用域是在函数内部。
def
example
(
)
:
v
=
1
#局部变量
print
(
v
)
由于局部变量作用域只在函数内部有效,因此程序会报错
Traceback
(
most recent call last
)
:
File
"test.py"
,
line
3
,
in
<
module
>
print
(
v
)
NameError
:
name
'v'
is
not
defined
全局变量
和局部变量相对,全局变量是定义在函数外的变量,因此具有更大的作用域。
v
=
1
def
example
(
)
:
print
(
v
)
example
(
)
运行结果
1
注意事项
python与C有许多不同的地方
(1)例子1
v
=
1
def
example
(
)
:
v
=
2
print
(
v
)
example
(
)
print
(
v
)
运行结果
2
1
当想要在函数中对全局变量进行赋值时,如上操作,python会生成新的局部变量,而不是修改全局变量的值,可以通过global标记在局部作用域中声明全局变量。
v
=
1
def
example
(
)
:
global
v
v
=
2
print
(
v
)
example
(
)
print
(
v
)
运行结果
2
2
(1)例子2
def
example
(
)
:
print
(
v
)
v
=
1
example
(
)
运行结果
1
由于python是解释性语言,不需要进行编译,程序运行时每次读源文件的一行代码,并执行相应的操作,因此上述代码可以正常运行,不会出现找不到变量v的问题。而当我们使用如下代码则会报错。
def
example
(
)
:
print
(
v
)
example
(
)
v
=
1
运行结果
Traceback
(
most recent call last
)
:
File
"test.py"
,
line
4
,
in
<
module
>
example
(
)
File
"test.py"
,
line
2
,
in
example
print
(
v
)
NameError
:
name
'v'
is
not
defined
闭包
在python中“一切皆对象”,因此函数也被看作对象,因此产生了一种特殊的形式函数嵌套。如下例子
def
out_func
(
)
:
a
=
1
b
=
2
def
iner_func
(
)
:
return
a
+
1
return
iner_func
闭包是在内嵌函数生成的时候将其用到的环境以及自己本身都封装在了一起。为了直观理解闭包,我们介绍一下 code 对象, code 对象是指代码对象,表示编译成字节的的可执行Python代码,或者字节码。它有几个比较重要的属性:
- co_name: 函数的名称
- co_nlocals: 函数使用的局部变量的个数
- co_varnames: 一个包含局部变量名字的元组
- co_cellvars: 是一个元组,包含嵌套的函数所引用的局部变量的名字
- co_freevars: 是一个元组,保存使用了的外层作用域中的变量名
- co_consts: 是一个包含字节码使用的字面量的元组
有如下程序
def
out_func
(
)
:
a
=
1
b
=
2
c
=
3
def
iner_func1
(
)
:
return
a
+
1
def
iner_func2
(
)
:
return
b
+
1
return
iner_func1
func
=
out_func
(
)
print
(
out_func
.
__code__
.
co_varnames
)
print
(
out_func
.
__code__
.
co_cellvars
)
print
(
out_func
.
__code__
.
co_freevars
)
print
(
func
.
__code__
.
co_varnames
)
print
(
func
.
__code__
.
co_cellvars
)
print
(
func
.
__code__
.
co_freevars
)
运行结果
(
'c'
,
'iner_func1'
,
'iner_func2'
)
(
'a'
,
'b'
)
(
)
(
)
(
)
(
'a'
,
)
前三个输出是关于外部函数的一些属性,由于out_func函数中声明了几个局部变量,因此包含三个变量。第二个输出是内部的函数用到的变量,而iner_func1与iner_func2用到了变量a与b。第三个输出是使用了的外部作用域的变量,因此是空。
后三个输出是关于内部函数的一些属性,前两个输出由于iner_func1没有使用到因此为空,由于iner_func1用到了外部变量a,因此只有变量a。
若是采用三层的嵌套
def
outout_func
(
)
:
c
=
3
def
out_func
(
)
:
a
=
1
b
=
2
def
iner_func
(
)
:
return
a
+
1
+
c
return
iner_func
return
out_func
(
)
func
=
outout_func
(
)
print
(
func
.
__code__
.
co_varnames
)
print
(
func
.
__code__
.
co_cellvars
)
print
(
func
.
__code__
.
co_freevars
)
运行结果
(
)
(
)
(
'a'
,
'c'
)
在学习闭包的过程中,我个人对一下几种情况有点蒙圈。
程序一
f
=
[
]
v
=
[
0
]
for
i
in
range
(
5
)
:
v
[
0
]
=
i
def
b
(
)
:
x
=
v
return
x
f
.
append
(
b
)
for
i
in
range
(
5
)
:
print
(
f
[
i
]
(
)
)
print
(
id
(
f
[
i
]
(
)
)
)
运行结果
[
4
]
1618988720712
[
4
]
1618988720712
[
4
]
1618988720712
[
4
]
1618988720712
[
4
]
1618988720712
程序二
f
=
[
]
v
=
[
0
]
for
i
in
range
(
5
)
:
def
b
(
)
:
x
=
v
v
[
0
]
=
i
return
x
f
.
append
(
b
)
for
i
in
range
(
5
)
:
print
(
f
[
i
]
(
)
,
id
(
f
[
i
]
(
)
)
)
运行结果
[
0
]
1397297537608
[
1
]
1397297537608
[
2
]
1397297537608
[
3
]
1397297537608
[
4
]
1397297537608
两个程序类似,但是输出完全不同。
第一个程序之所以输出全是相同的,是因为在闭包生成的过程中打包了 v 的地址作为闭包环境。在函数调用时将 x 指向了相同的地址,也就是 v 的地址,而在函数调用之前, v 进行了5次赋值,最后一次被赋为了4,因此每次的输出均相同。
第二个程序之所以输出不同,是因为闭包在生成过程中将 v 的地址以及 i 的地址都打包了,而 i 指向的是常数,因此具有不同的地址。所以当函数运行时, x 先指向了 v 的地址,之后 v 中的数字被替换为 i 指向的不同常量,因此具有不同的输出。
在这里若我们将 i 替换为列表会发生什么呢,有如下程序
f
=
[
]
ha
=
[
0
]
z
=
[
0
]
for
i
in
range
(
5
)
:
z
[
0
]
=
i
def
b
(
)
:
x
=
ha
ha
[
0
]
=
z
[
0
]
return
x
f
.
append
(
b
)
for
i
in
range
(
5
)
:
print
(
f
[
i
]
(
)
,
id
(
f
[
i
]
(
)
)
)
运行结果
[
4
]
2863541936712
[
4
]
2863541936712
[
4
]
2863541936712
[
4
]
2863541936712
[
4
]
2863541936712