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.funcmodule 是一个模块对象,func 是这个模块的一部分内容(function),我们称之为 属性。也就是说,funcmodule 的一个属性。

各个命名空间创建的时间是不一样的,而且有着不同的生命周期:

  • 内置名称的命名空间在 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 作用域里面没有 ab,就会往上层搜索


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 能够将变量绑定为localglobal(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
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> class A :
a = 0
def func(self) :
b = a + 1
print(b)

>>> A.func # 查看类中 func 函数的属性
<function A.func at 0x00000038A3AF4488>
>>> A().func
<bound method A.func of <__main__.A object at 0x000000D5EB971400>>
>>> A.func()
TypeError: func() missing 1 required positional argument: 'self'
>>> A().func() # 执行类中 func 函数
NameError: name 'a' is not defined
  • Python 类中的方法的第一个参数通常是self,这是一个约定。如果不遵循这个约定,对其他 Python 程序员而言你的代码可读性就会变差,而且有些浏览器程序也可能遵循此约定编写的。
    self 是类方法的第一个参数,它就是类的实例对象自己,当调用方法时:
    A().func() 等同于 A().func(A)
    然后,类中的方法就可以通过 self 来调用该方法外面的方法或者变量了。这和 C++ 中隐式的 this 类似。

  • 类的定义就像函数定义,要先执行才能生效。所以,定义了一个类无论有没有错误(不包括定义类时出现语法错误),都不会报错。因此,查看类的 attribute 时(如,A.func A().func),无论这个类有没有错误,都不会报错。但是当执行这个类的时候(如,A().func()),如果错误就会报错。
    同时,因为类中的方法有参数 self,所以,在实例化类的时候要加括号A()self 表示传进去的时类本身,所以可以不用填,但括号一定要。
    如果,定义的类中不用传参,实例化类的时候是可以不用括号的。

1
2
3
4
5
6
7
8
9
10
>>> class B :
a = 0
def func() :
b = 'hello'
print(b)

>>> B.a
0
>>> B.func()
hello
  • 类定义时,会创建一个新的命名空间,作为局部作用域。因此,在类中定义的变量(第一层)和函数名会成为这个新命名空间的局部变量。
    定义一个函数也会创建一个命名空间,因此,在类中定义一个函数时,这个函数里面的变量是属于这个函数命名空间的。类的命名空间的变量不能够直接调用函数内的变量,函数内也不能过直接调用类命名空间的变量。如果想要调用该方法外面的变量或者方法,可以通过 self 来实现
1
2
3
4
5
6
7
8
>>> class A :
a = 0
def func(self) :
b = self.a + 1
print(b)

>>> A().func()
1

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