Python - Scope and namespace
Namespace
A namespace ia a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries.
命名空间是从名称到对象的映射。当前,命名空间主要通过 Python 字典来实现。
Examples of namespaces are:
- The set of built-in names (containing functions such as
abs()
, and built-in exception names)
内置名称集(包括内置函数,如abs()
,和内置异常的名称) - The global names in a module
模块中的全局名称 - The local names in a function invocation
函数调用中的局部名称
and so on.
不同命名空间的名称没有任何关系。例如,两个不同模块都可以定义函数 func()
,而不会产生混淆。只要在使用的时候加上模块名作为前缀引用它们就可以了。
module.func
,module
是一个模块对象,func
是这个模块的一部分内容(function),我们称之为 属性。也就是说,func
是 module
的一个属性。
各个命名空间创建的时间是不一样的,而且有着不同的生命周期:
- 内置名称的命名空间在 Python 解析器启动时创建,永远不会被删除
- 模块的全局命名空间在读入模块定义时创建。通常情况下,模块命名空间也会一直保存到解析器退出。
- 函数的局部命名空间在函数调用时创建,在函数返回或者引发了一个函数内部没有处理的异常时删除。
Scope
作用域 是 Python 程序中可以直接访问命名空间的代码区域。
直接访问,是指用没有前缀的引用在命名空间中找到相应的名称。如,abs(-1)
,abs
属于直接访问。
属性访问,是指需要用点分.
模式来指定属性的访问。如,module.func()
。
在 Python 程序运行中,至少有 4 个 scopes 是存在的。
- Local(innermost),包含局部变量。如,function 内部的变量。
- Enclosing,包含了非局部(non-local)也非全局(non-global)的变量。例如,两个嵌套函数,内层函数可能搜索外层函数的 namespace,但该 namespace 对内层函数而言既非局部也非全局。
- Global(next-to-last),当前脚本的最外层。如,当前模块的全局变量。
- Built-in(outtermost),Python builtin module。Containing bulit-in functions / built-in values / keywords and so on.
著名的 ‘LEGB-rule’,即 scope 的搜索顺序:
Local -> Enclosing -> Global -> Built-in
- 首先,搜索最里面包含局部命名的作用域
- 其次,从里向外搜索所有父函数的作用域,其中的命名既非局部也非全局。(该域不一定存在)
- 接着,再往上搜索的作用域是当前模块全局命名的作用域,即函数定义所在的模块的命名空间
- 最后,搜索的是包含内置命名的命名空间作用域
对于最终都没有搜索到的命名,Python 会抛出一个 NameError 异常。
Example1:1
2
3
4
5
6
7
8
9
10
11>>> def outer() :
a = 0
b = 1
def inner() :
print(a)
print(b)
inner()
>>> outer()
0
1
当执行 inner()
的时候,发现 Local 作用域里面没有 a
和 b
,就会往上层搜索
Example2:1
2
3
4
5
6
7
8
9
10
11
12>>> def outer() :
a = 0
b = 1
def inner() :
print(a) # non-local namespace 'a'
b = 2
print(b) # Locale namespace 'b'
inner()
>>> outer()
0
2
Example3:1
2
3
4
5
6
7
8
9
10
11
12>>> def outer() :
a = 0
b = 1
def inner() :
print(a) # Local namespace 'a'
print(b) # Local namespace 'b'
b = 2
inner()
>>> outer()
0
UnboundLocalError: local variable 'b' referenced before assignment
Python 局部变量不能够在定义之前引用
总结:
- 局部赋值语句通常会隐式地创建一个局部变量,即便该变量名已存在于赋值语句发生的上一层作用域中。
- 如果没有 global 关键字声明变量,对一个变量的赋值总是认为该变量存在于最内层(innermost)的作用域中
local
non-local
global
local
:一般在没有说明的情况下赋值都默认为 local
的。nonlocal
:默认情况下变量的作用范围是local
的,nonlocal
能够将变量绑定为local
到global
(excluding)之间作用域。i.e.,如果你说明了一个 nonlocal
变量,这个命名的作用域就是从本地到整个模块之间(excluding global)。比如,函数外有一个变量 a
,函数内说明一个 nonlocal a
变量,则函数里面的这个 a
其实就是函数外面的 a
的值。如果,对函数里面的 a
赋值,则调用这个函数之后,函数外面的 a
的值就已经改变了。global
:说明全局变量,这个变量的作用域是整个模块。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23>>> def scope_test() :
def do_local() :
spam = 'local spam'
def do_nonlocal() :
nonlocal spam
spam = 'nonlocal spam'
def do_global() :
global spam
spam = 'global spam'
spam = 'test spam'
do_local()
print('After local assignment: ', spam) # print nonlocal 'spam'
do_nonlocal()
print('After nonlocal assignment: ', spam) # print nonlocal 'spam', but it has been change
do_global()
print('After global assignment: ', spam) # print nonlocal 'spam', the function just change the global 'spam'
>>> scope_test()
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
>>> print('In global scope: ', spam)
In global scope: global spam
The Scope and Namespace of Class
1 | >>> class A : |
Python 类中的方法的第一个参数通常是
self
,这是一个约定。如果不遵循这个约定,对其他 Python 程序员而言你的代码可读性就会变差,而且有些浏览器程序也可能遵循此约定编写的。self
是类方法的第一个参数,它就是类的实例对象自己,当调用方法时:A().func()
等同于A().func(A)
然后,类中的方法就可以通过self
来调用该方法外面的方法或者变量了。这和 C++ 中隐式的this
类似。类的定义就像函数定义,要先执行才能生效。所以,定义了一个类无论有没有错误(不包括定义类时出现语法错误),都不会报错。因此,查看类的 attribute 时(如,
A.func
A().func
),无论这个类有没有错误,都不会报错。但是当执行这个类的时候(如,A().func()
),如果错误就会报错。
同时,因为类中的方法有参数self
,所以,在实例化类的时候要加括号A()
。self
表示传进去的时类本身,所以可以不用填,但括号一定要。
如果,定义的类中不用传参,实例化类的时候是可以不用括号的。
1 | >>> class B : |
- 类定义时,会创建一个新的命名空间,作为局部作用域。因此,在类中定义的变量(第一层)和函数名会成为这个新命名空间的局部变量。
定义一个函数也会创建一个命名空间,因此,在类中定义一个函数时,这个函数里面的变量是属于这个函数命名空间的。类的命名空间的变量不能够直接调用函数内的变量,函数内也不能过直接调用类命名空间的变量。如果想要调用该方法外面的变量或者方法,可以通过self
来实现
1 | >>> class A : |
List Comprehension (列表推导式/列表解析)[expression for varible in iterable]
or list(expression for varible in iterable)
Generator Expression (生成器表达式)(expression for varible in iterable)
List Comprehension 和 Generator Expression 都会创建一个新的 namespace,里面的 varible
的作用域只是在这个命名空间。所以,当执行完一个 List Comprehension 或 Generator Expression 的时候,里面的 varible
都不会存留下来。1
2
3
4
5
6
7
8
9
10
11
12>>> [i for i in range(0, 10, 2)]
[0, 2, 4, 6, 8]
>>> print(i)
NameError: name 'i' is not defined
>>> list(i for i in range(0, 10, 2))
[0, 2, 4, 6, 8]
>>> print(i)
NameError: name 'i' is not defined
>>> (i for i in range(0, 10, 2))
<generator object <genexpr> at 0x000000734276DDB0>
>>> i
NameError: name 'i' is not defined
因此,在 List Comprehension 或者 Generator Expression 调用外部的变量也会出错。1
2
3
4
5>>> class A :
a = 3
b = list(a + i for i in range(10)) # 'i' 输入 range(10),'a' 没有定义
NameError: name 'a' is not defined