先来看一个问题:
x = 2
def func(x):
x = 3
print(x)
func()
print x
这段程序的输出结果是
3
2
如果你知道为什么输出这样的结果而且知道里面的机制,接下来的文章可以不看了,如果不了解,可以继续看下去。
作用域介绍
当我们在代码里使用变量时,Python创建对象,改变对象 或 查找对象都是在一个所谓命名空间下进行的(一个保存变量名的地方)。
而函数除了打包代码之外,还定义了一个新的变量空间,一个函数所有的变量,都与函数的命名空间相关联:
- def 内定义的变量名能够被 def内的代码使用,不能在函数外部引用这样的变量名
- def之中的变量名与def之外的变量名并不冲突
也就是说:
- 如果一个变量在def内被赋值,它就被定义在这个函数之内
- 如果在def之外赋值,它就是整个文件全局的
那么回到一开始的问题:
x = 2
def func(x):
x = 3
尽管这两个变量名都是x
,但是他们作用域(命名空间)可以把他们区别开。作用域(命名空间)有助于防止程序之间变量名的冲突,而且,有助于函数成为更加独立的单元。
在Python中,函数定义了一个函数本地内的作用域,而像x = 2
这样赋值语句定义了一个全局作用域(模块级别的变量,使用范围仅限于单个文件)。 要注意的是,每次对函数的调用都会创建一个新的本地作用域。所有的变量名都可以分为本地变量 全局变量 或者Python内置变量。
在交互模式下(如 ipython ,jupyter notebook)赋值的变量,都位于一个叫__main__
的内置模块中,以下是在ipython中 使用a = 2
后,查看__main__
模块的结果。
In [2]: import __main__
In [3]: a = 2
In [4]: dir(__main__)
Out[4]:
['In',
'Out',
'_',
'__',
'___',
'__builtin__',
'__builtins__',
'__doc__',
'__main__',
'__name__',
'__package__',
'_dh',
'_i',
'_i1',
'_i2',
'_i3',
'_i4',
'_ih',
'_ii',
'_iii',
'_oh',
'_sh',
'a',
'exit',
'get_ipython',
'quit']
在__builtin__
模块中,有Python预定义的变量名,我们同样可以通过dir(__builtin__)
进行查看。
In [5]: dir(__builtin__)
Out[5]:
['ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
'BufferError',
'BytesWarning',
'DeprecationWarning',
'EOFError',
'Ellipsis',
'EnvironmentError',
'Exception',
'False',
'FloatingPointError',
'FutureWarning',
'GeneratorExit',
'IOError',
'ImportError',
'ImportWarning',
'IndentationError',
'IndexError',
'KeyError',
'KeyboardInterrupt',
'LookupError',
'MemoryError',
'NameError',
'None',
'NotImplemented',
'NotImplementedError',
'OSError',
'OverflowError',
'PendingDeprecationWarning',
'ReferenceError',
'RuntimeError',
'RuntimeWarning',
'StandardError',
'StopIteration',
'SyntaxError',
'SyntaxWarning',
'SystemError',
'SystemExit',
'TabError',
'True',
'TypeError',
'UnboundLocalError',
'UnicodeDecodeError',
'UnicodeEncodeError',
'UnicodeError',
'UnicodeTranslateError',
'UnicodeWarning',
'UserWarning',
'ValueError',
'Warning',
'ZeroDivisionError',
'__IPYTHON__',
'__debug__',
'__doc__',
'__import__',
'__name__',
'__package__',
'abs',
'all',
'any',
'apply',
'basestring',
'bin',
'bool',
'buffer',
'bytearray',
'bytes',
'callable',
'chr',
'classmethod',
'cmp',
'coerce',
'compile',
'complex',
'copyright',
'credits',
'delattr',
'dict',
'dir',
'display',
'divmod',
'dreload',
'enumerate',
'eval',
'execfile',
'file',
'filter',
'float',
'format',
'frozenset',
'get_ipython',
'getattr',
'globals',
'hasattr',
'hash',
'help',
'hex',
'id',
'input',
'int',
'intern',
'isinstance',
'issubclass',
'iter',
'len',
'license',
'list',
'locals',
'long',
'map',
'max',
'memoryview',
'min',
'next',
'object',
'oct',
'open',
'ord',
'pow',
'print',
'property',
'range',
'raw_input',
'reduce',
'reload',
'repr',
'reversed',
'round',
'set',
'setattr',
'slice',
'sorted',
'staticmethod',
'str',
'sum',
'super',
'tuple',
'type',
'unichr',
'unicode',
'vars',
'xrange',
'zip']
可以看到,预定义的包括python的关键字,异常,和一些内置模块等变量名。
在Python2.0及之前的版本中,Python只支持3种作用域:局部作用域,全局作用域,内置作用域; 在python2.2中,引入了一种新的作用域:嵌套作用域,本质上就是Python实现了闭包,相应的,变量查找顺序由之前的LGB变成了LEGB(L:Local-本地作用域, E:Enclosing-上一层结构中的本地作用域, G:Global-全局作用域, B:Built-in - 内置作用域),变量名的引用分为4个作用域查找,首先是本地,之后是上个函数内,之后是全局,最后是去内置空间查找。
聊了LEGB变量查找原则之后,再看看如果需要在函数内部引用全局变量需要怎么做:
x = 3
def use_global_param():
global x
x = 3 - 2
return x
print use_global_param()
这样输出的结果就是1
。但是这样做不太好,引入全局变量后,就会破坏函数的独立性,让程序变得难以理解和使用。比如以下这个例子
x = 5
def func():
global x
x = 6
def func2():
global x
x = 8
那执行交替执行两个函数后,x的值到底是多少呢?
在Python里,函数是可以嵌套声明的,比如我们可以这么声明函数:
def out():
x = 1
def inner():
x = 2
print(x)
inner()
out()
这个程序的运行结果是2
,如果想操作out
函数里面的变量,我们可以引入Python3提供的nonlocal
关键字:
def out():
x = 1
def inner():
nonlocal x
print(x)
inner()
out()
这样输出结果就是1
.
最后来看一些代码片段,如果读者心中的结果和编译器的结果一致,那就说明函数作用域这块理解了。
x = 'spam'
def func():
print x
func()
x = 'spam'
def func():
x = 'hello'
func()
print(x)
x = 'spam'
def func():
x = 'hello'
print(x)
func()
print(x)
x = 'spam'
def func():
global x
x = 'hello'
func()
print(x)
x = 'spam'
def func():
x = 'hello'
def inner():
print(x)
inner()
func()
print(x)
def func():
x = 'hello'
def inner():
nonlocal x
x = 'spam'
inner()
print(x)
func()
欢迎把答案写在下面!