Python中的块作用域
使用其他语言编写代码时,有时会创建块作用域,如下所示:Python中的块作用域,python,scope,Python,Scope,使用其他语言编写代码时,有时会创建块作用域,如下所示: statement ... statement { statement ... statement } statement ... statement 一个(许多)目的是提高代码可读性:显示某些语句构成逻辑单元,或者某些局部变量仅在该块中使用 Python中有没有一种惯用的方法来做同样的事情?Python中惯用的方法是保持函数的简短。如果您认为需要,请重构您的代码!:) Python为每个模块、类、函数、生成器表达
statement
...
statement
{
statement
...
statement
}
statement
...
statement
一个(许多)目的是提高代码可读性:显示某些语句构成逻辑单元,或者某些局部变量仅在该块中使用
Python中有没有一种惯用的方法来做同样的事情?Python中惯用的方法是保持函数的简短。如果您认为需要,请重构您的代码!:)
Python为每个模块、类、函数、生成器表达式、dict理解、set理解创建了一个新的作用域,在Python3.x中,也为每个列表理解创建了一个新的作用域。除此之外,函数内部没有嵌套的作用域。不,没有创建块作用域的语言支持 以下构造创建范围:
- 模块
- 阶级
- 功能(包括lambda)
- 生成器表达式
- 理解(dict、set、list(在Python3.x中))
def my_func():
shared_variable = calculate_thing()
def do_first_thing():
... = shared_variable
do_first_thing()
def do_second_thing():
foo(shared_variable)
...
do_second_thing()
如果你不确定为什么你可能想这样做,那么可能会说服你
基本原则是尽可能严格地限定所有内容的范围,而不将任何“垃圾”(额外类型/函数)引入到比绝对需要的范围更广的范围内-没有任何其他内容想要使用
do_first_thing()
方法,因此不应将其范围限定在调用函数之外。我同意没有块范围。但是Python3中的一个地方使它看起来好像有块作用域
发生了什么事,让人看了这个样子
这在Python2中正常工作,但为了使变量泄漏在Python3中停止,他们已经完成了这个技巧,这个更改使它看起来好像在这里有块作用域
让我解释一下
根据作用域的思想,当我们在同一作用域内引入同名变量时,其值应该被修改 这就是Python 2中发生的情况:
>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'
但是在Python3中,即使引入了同名变量,它也不会重写,出于某种原因,列表理解就像一个沙盒,似乎在其中创建了一个新的作用域
>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'
这个答案与答案相反创建作用域的唯一方法是函数、类或模块,因为这看起来像是创建新作用域的另一个地方。模块(和包)是将程序划分为单独名称空间的一种很好的python方法,这似乎是这个问题的一个隐含目标。事实上,当我学习Python的基础知识时,我对缺少块作用域特性感到沮丧。然而,一旦我理解了Python模块,我就可以更优雅地实现我以前的目标,而不需要块范围
作为动机,为了给人们指明正确的方向,我认为给出一些Python范围结构的明确示例是很有用的。首先,我解释了使用Python类实现块作用域的失败尝试。接下来,我将解释如何使用Python模块实现更有用的功能。最后,我概述了包在加载和过滤数据方面的实际应用
正在尝试使用类阻止作用域
有那么一会儿,我认为我已经通过将代码粘贴到类声明中实现了块作用域:
x = 5
class BlockScopeAttempt:
x = 10
print(x) # Output: 10
print(x) # Output: 5
不幸的是,在定义函数时,这种情况会发生故障:
x = 5
class BlockScopeAttempt:
x = 10
print(x) # Output: 10
def printx2():
print(x)
printx2() # Output: 5!!!
这是因为类中定义的函数使用全局范围。解决此问题的最简单(但不是唯一)方法是显式指定类:
x = 5
class BlockScopeAttempt:
x = 10
print(x) # Output: 10
def printx2():
print(BlockScopeAttempt.x) # Added class name
printx2() # Output: 10
这并不是很优雅,因为人们必须根据函数是否包含在类中来编写不同的函数
使用Python模块获得更好的结果
模块与静态类非常相似,但根据我的经验,模块要干净得多。为了对模块执行相同的操作,我在当前工作目录中创建了一个名为my_module.py
的文件,其中包含以下内容:
x = 10
print(x) # (A)
def printx():
global x
print(x) # (B)
然后在我的主文件或交互式(例如Jupyter)会话中,我会
x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5
作为说明,每个Python文件都定义了一个具有自己的全局名称空间的模块。导入模块允许您使用
语法访问此命名空间中的变量
如果您在交互式会话中使用模块,可以在开始时执行这两行
%load_ext autoreload
%autoreload 2
当修改相应的文件时,模块将自动重新加载
用于加载和筛选数据的包
包的概念是模块概念的一个轻微扩展。包是一个目录,其中包含一个(可能是空的)\uuuu init\uuuuu.py
文件,该文件在导入时执行。可以使用
语法访问此目录中的模块/包
对于数据分析,我通常需要读取一个大数据文件,然后以交互方式应用各种过滤器。读取一个文件需要几分钟,所以我只想做一次。根据我在学校学到的关于面向对象编程的知识,我过去认为应该编写代码,以便在类中作为方法进行过滤和加载。这种方法的一个主要缺点是,如果我重新定义过滤器,类的定义就会改变,因此我必须重新加载整个类,包括数据
现在使用Python,我定义了一个名为myu data
的包,其中包含名为load
和filter
的子模块。在filter.py的内部,我可以进行相对导入:
from .load import raw_data
如果修改filter.py
,则autoreload
将检测到更改。它不会重新加载load.py
,因此我不需要重新加载数据。通过这种方式,我可以在Jupyter笔记本中创建过滤代码的原型,将其包装为函数,然后将粘贴从笔记本中直接剪切到filter.py
。弄明白了这一点,我的工作发生了革命性的变化
statement
statement
# Begin block
a = ...
b = ...
statement
statement
del a, b
# End block
statement
from scoping import scoping
a = 2
with scoping():
assert(2 == a)
a = 3
b = 4
scoping.keep('b')
assert(3 == a)
assert(2 == a)
assert(4 == b)