美文网首页Python学习分享
Python 的作用域的相关规则

Python 的作用域的相关规则

作者: 烟雨丿丶蓝 | 来源:发表于2019-06-09 14:18 被阅读21次

我并不喜欢 Python 的作用域的设计,但这门语言是如此流行,以至于很多时候你不得不去了解它. 本文试图比较全面地总结 Python 的作用域的相关规则. 若本文有错误之处,欢迎纠正.

本文基于 Python 3.6.8.

1、作用域划分

Python 中作用域的划分大致以“块”为单位. 什么是“块”呢?主要是模块、函数体、类定义(还有一些其他情况,例如函数 eval() 和 exec() 的字符串参数等). 所以,在 if / while / for 等语句中引入的变量会污染整个“块”:

def f():
... for i in range(10):
... pass
... print(i)
...
f()
9
2、名字查找

Python 的名字查找规则是符合直觉的:按照 local -> enclosing functions -> global -> built-in 的顺序由内而外查找,并选择最近的一个. 有一种说法叫 LEGB 规则(Local,Enclosing,Global,Built-in),可能是为了便于记忆,但我个人觉得直接按照直觉记忆即可.

在 Python 中,名字通过“绑定操作”引入. 以下这些结构执行“绑定操作”:函数参数、import 语句、类定义、函数定义,以及赋值、for 循环、with 或 except 中引入的新变量. 另外,del 语句可以取消一个名字的绑定.

如果在当前块中绑定一个名字,这个名字会被默认为是当前块中的,除非被声明为 nonlocal 或 global.

然而,Python 不区分“通过赋值操作引入新变量”和“给已有的变量赋值”. 当我们试图在内层作用域中修改外层变量时,这个特性就会将问题搞复杂. 比如说:

def f():
... a = 0
... def g():
... a = 1
... g()
... print(a)
...
f()
0
在这里,g 中的语句 a = 1 实际上是在 g 中引入了一个新变量,所以外层的 a 并没有被改变. 要想改变外层的 a, 就需要使用 nonlocal 语句,它会在 enclosing functions 中由内而外查找相应的变量:

def f():
... a = 0
... def g():
... nonlocal a
... a = 1
... g()
... print(a)
...
f()
1
相应地,要想在内层作用域中改变全局变量,就要使用 global 语句,它会按照 global -> built-in 的顺序由内而外查找相应的变量.

然而,在内层作用域中可以直接读取最近的外层 / 全局变量(只要当前作用域内没有重名的变量),而不需要 nonlocal / global 之类的语句:

def f():
... a = 100
... def g():
... print(a)
... g()
...
f()
100
注意,类定义也是一个名字空间,但它的影响范围不会延伸到方法中。所以,一个方法如果想要使用类变量或类中的其它方法,必须通过 self 参数:

class A:
... a = 11
... def f(self):
... print(20)
... def g(self):
... print(self.a)
... self.f()

def f():
... for i in range(10):
... pass
... print(i)
...
>>> f()
9
2、名字查找

Python 的名字查找规则是符合直觉的:按照 local -> enclosing functions -> global -> built-in 的顺序由内而外查找,并选择最近的一个. 有一种说法叫 LEGB 规则(Local,Enclosing,Global,Built-in),可能是为了便于记忆,但我个人觉得直接按照直觉记忆即可.

在 Python 中,名字通过“绑定操作”引入. 以下这些结构执行“绑定操作”:函数参数、import 语句、类定义、函数定义,以及赋值、for 循环、with 或 except 中引入的新变量. 另外,del 语句可以取消一个名字的绑定.

如果在当前块中绑定一个名字,这个名字会被默认为是当前块中的,除非被声明为 nonlocal 或 global.

然而,Python 不区分“通过赋值操作引入新变量”和“给已有的变量赋值”. 当我们试图在内层作用域中修改外层变量时,这个特性就会将问题搞复杂. 比如说:

>>> def f():
... a = 0
... def g():
... a = 1
... g()
... print(a)
...
>>> f()
0
在这里,g 中的语句 a = 1 实际上是在 g 中引入了一个新变量,所以外层的 a 并没有被改变. 要想改变外层的 a, 就需要使用 nonlocal 语句,它会在 enclosing functions 中由内而外查找相应的变量:

>>> def f():
... a = 0
... def g():
... nonlocal a
... a = 1
... g()
... print(a)
...
>>> f()
1
相应地,要想在内层作用域中改变全局变量,就要使用 global 语句,它会按照 global -> built-in 的顺序由内而外查找相应的变量.

然而,在内层作用域中可以直接读取最近的外层 / 全局变量(只要当前作用域内没有重名的变量),而不需要 nonlocal / global 之类的语句:

>>> def f():
... a = 100
... def g():
... print(a)
... g()
...
>>> f()
100
注意,类定义也是一个名字空间,但它的影响范围不会延伸到方法中。所以,一个方法如果想要使用类变量或类中的其它方法,必须通过 self 参数:

>>> class A:
... a = 11
... def f(self):
... print(20)
... def g(self):
... print(self.a)
... self.f()
...
x = A()
x.g()
11
20
3、名字绑定的影响是“前后双向”的

如果你在一个“块”中的任何地方绑定一个名字,该“块”中所有对该名字的使用都会被当成是指向当前“块”的. 这意味着以下的代码会报“赋值前使用”的错,因为这里 print(a) 中的 a 被认为是之后的 for 循环中的 a,即使外层有一个 a 也无济于事:

def f():
... a = 100
... def g():
... print(a)
... for a in range(10):
... pass
... g()
...
f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in f
File "<stdin>", line 4, in g
UnboundLocalError: local variable 'a' referenced before assignment
这时候,只要将 for 循环中的变量改个名字,print(a) 中的 a 就会被正常的名字查找过程在外层找到:

def f():
... a = 100
... def g():
... print(a)
... for b in range(10):
... pass
... g()
...
f()
100

  1. Lexical Scoping or Dynamic Scoping

Python 是 lexical scoping. 比如说:

def f():
... x = 1
... def g():
... print(x)
... def h():
... x = 2
... g()
... h()
...
f()
1
虽然作用域是被静态决定的,但它们是被“动态使用”的。考虑下面的例子:

def f():
... def g():
... print(x)
... x = 1
... g()
...
f()
1
在这里,g 被定义时尚未有 x 的存在,但当 g 被调用时却可以使用 g 定义后引入的 x. 不过,g 中使用的 x 仍然属于 g 被定义时的外层作用域(即 f 的内部),从这个意义上说,此处的作用域规则仍然是 lexical scoping.

要注意的是,根据 Python 官方 Tutorial 的说法,Python 正在向“静态名字解析”方向演化,所以请勿依赖这种动态名字解析!

Python学习交流群:835017344,这里是python学习者聚集地,有大牛答疑,有资源共享!有想学习python编程的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入学习。

相关文章

  • Python 的作用域的相关规则

    我并不喜欢 Python 的作用域的设计,但这门语言是如此流行,以至于很多时候你不得不去了解它. 本文试图比较全面...

  • Python装饰器与闭包!

    闭包是Python装饰器的基础。要理解闭包,先要了解Python中的变量作用域规则。 变量作用域规则 首先,在函数...

  • Python 中的作用域规则和闭包简析

    在对Python中的闭包进行简单分析之前,我们先了解一下Python中的作用域规则。关于Python中作用域的详细...

  • #抬抬小手学Python# Python 之作用域下的 glob

    global 和 nonlocal 作用域 该部分内容涉及 Python 变量作用域相关知识,变量作用域指的是变量...

  • 5-3 变量作用域

    在Python中,作用域可以分为: 内置作用域:Python预先定义的 全局作用域:所编写的整个程序 局部作用域:...

  • 变量作用域

    python的域规则 -变量作用域:在Python程序中创建、改变、查找变量名时,都是在一个保存变量名的空间中进行...

  • python基础2.2 True+True

    python的作用域规则:LEGB原则 为什么Python会认为两个完全不同的对象拥有相等的值?在Python中布...

  • 一周一章前端书·第2周:《你不知道的JavaScript(上)》

    第2章:词法作用域 第一章提到,所谓作用域就是JavaScript变量的存取规则。 而众多编程语言的作用域规则中,...

  • Python的闭包与装饰器

    一、python的函数作用域 python的函数作用域的含义,简而言之,即函数执行时变量所在的作用域。在pytho...

  • 浅谈JS作用域链

    浅谈JS作用域链 作用域 作用域(scope)就是变量访问规则的有效范围。作用域外,无法引用作用域内的变量;离开作...

网友评论

    本文标题:Python 的作用域的相关规则

    本文链接:https://www.haomeiwen.com/subject/bdfnxctx.html