Python中的块范围


93

当您使用其他语言编写代码时,有时会创建一个块作用域,如下所示:

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

(很多)目的之一是提高代码的可读性:表明某些语句形成一个逻辑单元或某些局部变量仅在该块中使用。

在Python中是否有惯用的方式做同样的事情?


2
One purpose (of many) is to improve code readability-正确编写(即遵循zen的python)的Python代码将不需要这种修饰即可读取。实际上,这是我喜欢Python的(许多)事情之一。
Burhan Khalid

我尝试使用__exit__with声明,更改,globals()但失败了。
Ruggero Turra

1
定义与资源获取有关的可变生命周期将非常有用
Ruggero Turra,

25
@BurhanKhalid:事实并非如此。Python的禅宗并没有阻止您在各处使用临时变量污染本地范围。如果将单个临时变量的所有用法转换为例如定义一个立即调用的嵌套函数,那么禅宗的Python也将不满意。明确限制变量的范围提高可读性的工具,因为它直接回答“下面是否使用这些标识符?” -即使阅读最精美的Python代码也会出现这个问题。
bluenote10'6

18
@BurhanKhalid没有功能可以。但是称其为“禅宗”只是令人作呕。
菲尔(Phil)

Answers:


81

不,创建块范围没有语言支持。

以下构造创建范围:

  • 模组
  • 函数(包括lambda)
  • 生成器表达式
  • 理解(dict,set,list(在Python 3.x中))

38

Python中的惯用方式是使函数简短。如果您认为需要此代码,请重构代码!:)

Python为每个模块,类,函数,生成器表达式,dict理解,集合理解以及在Python 3.x中为每个列表理解创建新作用域。除此之外,函数内部没有嵌套作用域。


12
“编程中最重要的事情是赋予名称的能力。第二个最重要的事情是不必要求赋予名称。” 在大多数情况下,Python要求给范围(用于变量等)命名。在这方面,Python变量是第二重要的测试。
Krazy Glew,2016年

19
编程中最重要的事情是管理应用程序的依存关系和管理代码块范围的能力。匿名块使您可以限制回调的生存期,否则,您的回调仅使用一次,但在程序运行期间有效,这会导致全局范围混乱,并损害代码的可读性。
德米特里(Dmitry)

我只是注意到变量对于dict / set理解也是局部的。我尝试使用Python 2.7和3.3,但不确定是否与版本有关。
wjandrea

1
@wjandrea你是对的-添加到列表中。这些版本在Python版本之间应该没有任何区别。
Sven Marnach '19

4
我将重述最后一句话,因为您可以很好地在函数中创建函数。因此,函数内部有嵌套作用域。
ThomasH

18

您可以通过在函数内部声明一个函数然后立即调用它来在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()例如,其他任何人都不想使用该方法,因此不应将其限制在范围之外。调用函数。


这也是Google开发人员在TensorFlow教程中使用的方式,例如此处所示
Nino Filiu

13

我同意没有限制范围。但是python 3中的一个地方使其像SEEM一样具有块作用域。

发生了什么,给了这个外观?这在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'

这与答案@Thomas的说法背道而驰。创建范围的唯一方法是函数,类或模块,因为这看起来就像在创建新范围的另一个地方。


0

模块(和包)是将程序划分为单独的命名空间的一种很棒的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

修改相应文件后,模块会自动重新加载。

用于加载和过滤数据的软件包

包的概念是模块概念的略微扩展。软件包是一个包含(可能为空白)__init__.py文件的目录,该文件在导入时执行。可以使用.语法访问此目录中的模块/软件包。

对于数据分析,我经常需要读取一个大数据文件,然后以交互方式应用各种过滤器。读取文件需要几分钟,所以我只想做一次。基于我在学校中学到的有关面向对象编程的知识,我曾经认为应该编写用于过滤和加载的代码作为类中的方法。这种方法的主要缺点是,如果我随后重新定义过滤器,则类的定义会更改,因此必须重新加载整个类,包括数据。

如今,使用Python,我定义了一个名为的包my_data,其中包含名为load和的子模块filter。在filter.py我里面可以做一个相对导入:

from .load import raw_data

如果我修改filter.pyautoreload则将检测到更改。它不会重新加载load.py,所以我不需要重新加载数据。这样,我可以在Jupyter笔记本中创建过滤代码的原型,将其包装为一个函数,然后从笔记本中直接剪切粘贴到中filter.py。弄清楚这一点,彻底改变了我的工作流程,使我从“ Python禅”的怀疑者转变为信徒。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.