在Python中阻止范围

时间:2011-05-29 13:14:27

标签: python scope

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

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

一个目的(很多)是为了提高代码的可读性:显示某些语句构成一个逻辑单元,或者某些局部变量仅在该块中使用。

在Python中有同样的方式做同样的事情吗?

7 个答案:

答案 0 :(得分:62)

不,没有语言支持来创建块范围。创建范围的唯一方法是函数,类或模块。

答案 1 :(得分:33)

Python中惯用的方法是保持你的功能简短。如果您认为需要这个,请重构您的代码! :)

Python为每个模块,类,函数,生成器表达式,字典理解,集合理解创建了一个新的范围,并且在Python 3.x中也为每个列表理解创建了一个新的范围。除此之外,函数内部没有嵌套的范围。

答案 2 :(得分:11)

通过在函数内声明一个函数然后立即调用它,您可以在Python中执行类似于C ++块作用域的操作。例如:

do_first_thing()

如果您不确定为什么要这样做,那么get the HTTP headers (and set, delete them)可能会说服您。

基本原则是尽可能紧密地确定所有内容的范围,而不引入任何“垃圾”。 (额外的类型/功能)扩展到比绝对需要更广的范围 - 没有其他人想要使用$(function() { Highcharts.setOptions({ global: { useUTC: false } }); // Create the chart $('#container').highcharts('StockChart', { chart: { events: { load: function() { // set up the updating of the chart each second var series = this.series[0], hasPlotLine = false, $button = $('#button'), chart = $('#container').highcharts(), yAxis = chart.yAxis[0], plotLine, d, newY; yAxis.addPlotLine({ value: 66, color: 'red', width: 2, id: 'plot-line-1', label: { text: 66, align: 'right', y: newY, x: 0 } }); setInterval(function() { var x = (new Date()).getTime(), // current time y = Math.round(Math.random() * 100); series.addPoint([x, y], true, true); plotLine = yAxis.plotLinesAndBands[0].svgElem; d = plotLine.d.split(' '); newY = yAxis.toPixels(y) - d[2]; plotlabel = yAxis.plotLinesAndBands[0].label; plotlabel.animate({ translateY: newY, text: Highcharts.numberFormat(y, 2) }, { duration: 400, step: function() { $(this.element).html(Highcharts.numberFormat(this.textStr,2)); }, complete: function() { } }), plotLine.animate({ translateY: newY }, 400); }, 1000); } } }, rangeSelector: { buttons: [{ count: 1, type: 'minute', text: '1M' }, { count: 5, type: 'minute', text: '5M' }, { type: 'all', text: 'All' }], inputEnabled: false, selected: 0 }, title: { text: 'Live random data' }, yAxis: [{ opposite: false, title: { enabled: false } }], exporting: { enabled: false }, series: [{ name: 'Random data', data: (function() { // generate an array of random data var data = [], time = (new Date()).getTime(), i; for (i = -999; i <= 0; i += 1) { data.push([ time + i * 1000, Math.round(Math.random() * 100) ]); } return data; }()) }] }); }); 方法,所以它不应该在调用函数之外。

答案 3 :(得分:9)

我同意没有阻止范围。但是在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的陈述 创建范围的唯一方法是函数,类或模块 因为这看起来像另一个地方创建新范围。

答案 4 :(得分:1)

为了完整起见:您可以使用del结束局部变量的作用域。另请参阅When is del useful in Python?。不过,这当然不是惯用语。

statement
statement

# Begin block
a = ...
b = ...
statement
statement
del a, b
# End block

statement

答案 5 :(得分:1)

我想出了一个解决方案,它具有最简单的界面和最少的额外名称。

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 :(得分: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的程序包,其中包含名为loadfilter的子模块。在filter.py内部,可以进行相对导入:

from .load import raw_data

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