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中))

,可以通过在函数内声明函数并立即调用它来在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()
方法,因此不应将其范围限定在调用函数之外。

我同意没有块范围。但是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)