Python变量的作用域

2024-01-09 00:11:23
标签: python

作者: Sam(甄峰) sam_code@hotmail.com


0. 命名空间(Namespace):

0.1:Namespace定义:

A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。

一个Namespace就是一个名字和对象映射的区块或者说一张映射表。通过名字就可以找到对应的object. 提供了在项目中避免名字冲突的一种方法。各个NameSpace是独立的,不同Namespace的名字之间没有任何关联,所以一个Namespace内不能有重名,但不同的Namespace是可以有重名而没有任何影响的。


Python中常见的Namespace有:

A. 内置名称(built-in names). Python的内置名称。如函数名,异常名等。可以认为是Python内置函数映射表。

B. 全局名称(global names). 模块中定义的名称,记录了模块的变量,包含函数,类,其它导入的模块,模块级的变量和常量。可以认为是模块映射表。

C. 局部名称(local names). 函数中定义的名称,记录的函数的变量,包含函数的参数和局部定义的变量。可以认为是此函数的映射表。


 


0.2:Namespace的查找顺序:

命名空间查找顺序:

假设我们要使用一个变量,则 Python 的查找顺序为:local names -> global names -> built-in names

如果找不到此变量 ,它将放弃查找并引发一个 NameError 异常:

NameError: name 'xxxxx' is not defined

 

0.3:Namespace的生命周期:

A. 内置名称(built-in names) Namespace:在Python解释器启动时创建,在退出Python前不会删除。(因为函数等一直要用)

B. 全局名称(global names) Namespace:在模块定义被读入时创建。

C. 局部名称(local names) Namespace: 在这个函数被调用时被创建。在函数返回或抛出一个不在函数内部处理的错误时被删除。

一个程序运行时,回创建很多Namespace(可理解为Name-Object映射表)。它的主要作用就是在项目中避免名字冲突。


1.作用域(Scope):

作用域(Scope)是指Python一个文本区域,这个区域可以直接访问命名空间。

Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种:

 

  • L(Local):最内层,包含局部变量,比如一个函数/方法内部。
  • E(Enclosing)

:包含了非局部(non-local)也非全局(non-global)的变量。(嵌套函数中常见)

存在函数嵌套时,一级一级往上层函数的命名空间查找

  • G(Global)

:当前脚本的最外层,比如当前模块的全局变量。

  • B(Built-in)

: 包含了内建的变量/关键字等,最后被搜索。

访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。

规则顺序: L –> E –> G –> B

在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。

 

Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问

 

可以看到,在if 里定义的变量,在外面还是可以访问。这点与C++不同。

 


2. 全局变量和局部变量:

 

2.1:定义: 

全局变量指在函数外定义的变量,它的name-object映射表为Global Namespace. 生命周期和模块一样。除非程序结束运行,或者全局变量被删除(del x),才会被在Global Namespace中删除。

局部变量指在函数内定义的变量。 此函数被调用时,创建name-object映射表即Local Namespace. 函数调用结束则删除。

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问

可以看到,函数内可以访问全局变量,也可以访问局部变量。 但当函数内定义一个与全局变量同名的变量时,其实是个全新的局部变量,与函数外同名变量无关。这里的关键是:当我们需要修改一个全局变量时,Python解释器会觉得疑惑,这到底是个修改,还是个定义。所以它统一成定义一个新Local变量。这个理由会在嵌套函数中再次看到,也会在List, dict为何可以直接修改中再次看到。


2.2:函数内修改全局变量--global:

看到这里,就会有个疑问,既然函数内可以访问全局变量,那如何修改?

比如在上个例子中,

sub= num1/num2

这个语句被理解为创建一个新的局部变量。而不是修改全局变量的内容。

可以添加关键字 global。 申明这个变量为全局变量。

 

 global关键字使用时,有两点需要注意:

A. 在函数内使用global申明为全局变量前,不能使用这个变量。

sub=1

global sub

sub=num1/num2

则会报错:

SyntaxError: name 'sub' is assigned to before global declaration


B. 在全局作用域中,使用global申明,也会报错。

例如:

test = 4

global test

SyntaxError: name 'test' is assigned to before global declaration

 之前看到有文章说在  __main__内不需要global申明。其实可以用:“Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问” 解释。

 

2.3:嵌套函数中,内层函数修改外层函数的局部变量---nonlocal

使用nonlocal. 作用与global神似。

2.3.1: 嵌套函数访问外层函数的局部变量:

按照LEBG理论,完全可以访问:

可以看到,在嵌套函数内访问外层函数的局部变量没问题。


 

但当需要修改时,就遇到问题。这里的关键是:当我们需要修改一个外层函数局部变量时,Python解释器会觉得疑惑,这到底是个修改,还是个定义。所以它统一成定义一个新内层Local变量。这个理由会在List, dict为何可以直接修改中再次看到。


2.3.2:嵌套函数中定义与外层函数局部变量相同的变量:

可以看到,在内层函数中定义新的变量,和外层函数是隔离的。

所以修改怎么办,这就是nonlocal的作用。

2.3.3:使用nonlocal关键字申明:

注意:nonlocal 后面跟着的变量必须是外层函数的局部变量,不能是全局变量。

 

3. 思考:为何在函数中修改List和dict内容,不需要global?

例如:

可以看到,list直接修改内容,使用append就可以在函数内修改全局变量。而其它变量不行。类似的,dict也可以。

这是因为:在函数中 使用dat = 4 这样的语句,是有歧义的,它既可以理解为:创建一个局部变量,也可以理解为修改全局变量。所以Python把这个行为统一理解为:创建一个局部变量。如果要修改,则需要加global关键字申明这其实是使用全局变量。

而list.append等语句,则没有这个问题,因为这既是为已经有的list添加一个项,而不可能是创建一个新的list. 

所以,这里的关键是:这个行为是否是明确无歧义的。

 

 

阅读(0) 收藏(0) 转载(0) 举报/Report
相关阅读

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有