Python 中的块范围
- 2025-02-27 09:07:00
- admin 原创
- 49
问题描述:
当你使用其他语言编写代码时,你有时会创建一个块作用域,如下所示:
statement
...
statement
{
statement
...
statement
}
statement
...
statement
其中一个目的(众多目的之一)是提高代码的可读性:显示某些语句形成逻辑单元或某些局部变量仅在该块中使用。
在 Python 中是否有一种惯用的方法可以做同样的事情?
解决方案 1:
不,没有语言支持创建块范围。
以下构造创建范围:
模块
班级
函数(包括 lambda)
生成器表达式
理解(字典、集合、列表(在 Python 3 中))
解决方案 2:
Python 中的惯用方法是保持函数简短。如果您认为需要这样做,请重构您的代码!:)
Python 为每个模块、类、函数、生成器表达式、字典推导、集合推导以及 Python 3.x 中的每个列表推导都创建了新的作用域。除此之外,函数内部没有嵌套的作用域。
解决方案 3:
你可以在 Python 中执行类似于 C++ 块作用域的操作,方法是在函数内部声明一个函数,然后立即调用它。例如:
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()
例如,没有其他东西愿意使用该方法,因此它不应该被限定在调用函数之外的范围。
解决方案 4:
我同意没有块作用域。但是 Python 3 中有一个地方让它看起来好像有块作用域。
发生了什么事导致出现这种表情?
这在 Python 2 中可以正常工作,但是为了在 Python 3 中停止变量泄漏,他们使用了这个技巧,并且这个更改使它看起来好像在这里具有块范围。
让我解释一下。
按照作用域的思想,当我们在同一作用域内引入同名的变量时,其值应该被修改。
这是在 Python 2 中发生的情况:
>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'
但是在Python 3中,即使引入了同名变量,也不会覆盖,而且列表推导式由于某种原因就像一个沙箱,似乎在其中创建了一个新的作用域。
>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'
这个答案与回答者ThomasH 的陈述 相悖,创建范围的唯一方法是函数、类或模块,因为这看起来像是创建新范围的另一个地方。
解决方案 5:
我已经找到一个解决方案,它具有最简单的界面并且需要在代码中引入最少的额外名称。
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)
https://pypi.org/project/scoping/
解决方案 6:
为了完整性:您可以使用del结束局部变量的作用域。另请参阅Python 中的 del 何时有用?。不过,这肯定不是惯用的。
statement
statement
# Begin block
a = ...
b = ...
statement
statement
del a, b
# End block
statement
解决方案 7:
模块(和包)是一种很好的 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():
print(x) # (B)
def alter_x():
global x
x = 8
def do_nothing():
# Here x is local to the function.
x = 9
然后在我的主文件或交互式(例如 Jupyter)会话中,我这样做
x = 5
from my_module import printx, do_nothing, alter_x # Output: 10 from (A)
printx() # Output: 10 from (B)
do_nothing()
printx() # Output: 10
alter_x()
printx() # Output: 8
print(x) # Output: 5
from my_module import x # Copies current value from module
print(x) # Output: 8
x = 7
printx() # Output: 8
import my_module
my_module.x = 6
printx() # Output: 6
作为解释,每个 Python 文件都定义一个具有自己的全局命名空间的模块。该import my_module
命令允许您使用语法访问此命名空间中的变量.
。我认为模块就像静态类。
如果你在交互式会话中使用模块,则可以在开始时执行这两行
%load_ext autoreload
%autoreload 2
当相应文件被修改时,模块将自动重新加载。
加载和过滤数据的包
包的概念是模块概念的轻微扩展。包是一个包含__init__.py
文件(可能是空白的)的目录,该文件在导入时执行。可以使用.
语法访问此目录中的模块/包。
对于数据分析,我经常需要读取大型数据文件,然后以交互方式应用各种过滤器。读取文件需要几分钟,所以我只想做一次。根据我在学校学到的面向对象编程知识,我过去认为应该将过滤和加载的代码编写为类中的方法。这种方法的一个主要缺点是,如果我重新定义过滤器,我的类的定义就会发生变化,所以我必须重新加载整个类,包括数据。
现在使用 Python,我定义了一个名为 的包my_data
,其中包含名为load
和 的子模块filter
。在其中filter.py
我可以进行相对导入:
from .load import raw_data
如果我修改filter.py
,那么autoreload
将检测到更改。它不会重新加载load.py
,所以我不需要重新加载我的数据。这样我就可以在 Jupyter 笔记本中原型化我的过滤代码,将其包装为一个函数,然后直接从我的笔记本中剪切粘贴到filter.py
。搞清楚这一点彻底改变了我的工作流程,让我从一个怀疑论者变成了“Python 之禅”的信徒。
扫码咨询,免费领取项目管理大礼包!