26

在许多语言(和地方)中,通过创建这样的块来创建本地范围是一种很好的做法。

void foo()
{
     ... Do some stuff ...

     if(TRUE)
     {
         char a;
         int b;

         ... Do some more stuff ...
     }

     ... Do even more stuff ...
 }

如何在 python 中实现这一点而不会出现意外的缩进错误并且不使用某种if True: 技巧

4

12 回答 12

9

你为什么要在 python 中创建新的作用域?

在其他语言中这样做的正常原因是变量作用域,但这在 python 中不会发生。

if True:
    a = 10

print a
于 2009-02-12T15:57:19.583 回答
9

在 Python 中,作用域分为三种类型:全局、本地和类。您可以创建专门的“范围”字典以传递给 exec / eval()。此外,您可以使用嵌套范围(在另一个范围内定义一个函数)。我发现这些在我的所有代码中都足够了。

正如 Douglas Leeder 已经说过的,在其他语言中使用它的主要原因是变量作用域,而这在 Python 中并没有真正发生。此外,Python 是我用过的最易读的语言。做一些类似 if-true 技巧的事情(你说你想避免)会违背可读性。在这种情况下,我认为最好的办法是将代码重构为多个函数,或者使用单个作用域。我认为 Python 中可用的作用域足以涵盖所有可能的情况,因此本地作用域实际上并不是必需的。

于 2009-02-12T16:10:01.440 回答
5

如果您只想创建临时变量并在使用它们后立即对它们进行垃圾收集,您可以使用

del varname

当你不再想要它们时。

如果它只是为了美观,您可以使用注释或额外的换行符,但没有额外的缩进。

于 2009-02-12T16:11:08.007 回答
5

Python 恰好有两个作用域,本地和全局。函数中使用的变量在本地范围内,无论它们是在什么缩进级别创建的。调用嵌套函数将产生您正在寻找的效果。

def foo():
  a = 1

  def bar():
    b = 2
    print a, b #will print "1 2"

  bar()

和其他人一样,我不得不问你为什么要在函数中创建一个有限的范围。

于 2009-02-12T16:23:30.020 回答
3

列表理解(Python 3+)和生成器中的变量是本地的:

>>> i = 0
>>> [i+1 for i in range(10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> i
0

但是为什么你需要这个?

于 2009-02-12T15:57:57.430 回答
3

范围是 Python 程序的文本区域,其中命名空间可直接访问。这里的“可直接访问”意味着对名称的非限定引用试图在命名空间中找到该名称......

请阅读文档并澄清您的问题。

顺便说一句,你不需要if(TRUE){}在 C 中,一个简单{}的就足够了。

于 2009-02-12T16:21:43.550 回答
3

正如其他答案中所提到的,Python 中没有类似的功能来创建带有块的新范围,但是在编写脚本或 Jupyter Notebook 时,我经常(ab)使用类来引入新的命名空间以获得类似的效果。例如,在您可能有模型“Foo”、“Bar”等以及相关变量的笔记本中,您可能希望创建一个新范围以避免重用名称,例如

model = FooModel()
optimizer = FooOptimizer()
...
model = BarModel()
optimizer = BarOptimizer()

或后缀名称,例如

model_foo = ...
optimizer_foo = ...

model_bar = ...
optimizer_bar= ...

相反,您可以引入新的命名空间

class Foo:
    model = ...
    optimizer = ...
    loss = ....

class Bar:
    model = ...
    optimizer = ...
    loss = ...

然后访问变量为

Foo.model
Bar.optimizer
...

我发现以这种方式使用命名空间来创建新的范围使代码更具可读性且不易出错。

于 2019-07-29T17:08:54.820 回答
2

我认为这是一个明确的信号,表明是时候创建一个新函数并重构代码了。我认为没有理由创建这样的新范围。有什么理由吗?

于 2009-02-12T16:01:19.323 回答
1
def a():
    def b():
        pass
    b()

如果我只是想要一些额外的缩进或调试,我会使用if True:

于 2009-02-12T16:03:19.927 回答
1

像这样,对于任意名称t

### at top of function / script / outer scope (maybe just big jupyter cell)
try: t
except NameError:
    class t
        pass
else:
    raise NameError('please `del t` first')

#### Cut here -- you only need 1x of the above -- example usage below ###

t.tempone = 5 # make new temporary variable that definitely doesn't bother anything else.
# block of calls here...
t.temptwo = 'bar' # another one...
del t.tempone # you can have overlapping scopes this way

# more calls
t.tempthree = t.temptwo; del t.temptwo # done with that now too
print(t.tempthree)
# etc, etc -- any number of variables will fit into t.


### At end of outer scope, to return `t` to being 'unused'
del t

以上所有内容都可以在函数 def 中,或者只是脚本中 defs 之外的任何地方。

您可以随时将新元素添加或删除到任意命名的类中。你真的只需要其中之一——然后根据需要管理你的“临时”命名空间。

如果这是在函数体中,则该del t语句不是必需的,但是如果包含它,则可以复制/粘贴彼此相距很远的代码块,并让它们按您的期望工作(使用 't' 的不同用法完全独立,每次使用都以 thattry: t...块开头,以del t) 结尾。

这样,如果t已经用作变量,您会发现,并且它不会破坏 t,因此您可以找出它是什么。

这比使用一系列 random=named 函数只调用一次更不容易出错——因为它避免了处理它们的名称,或者记住在定义后调用它们,特别是如果你必须重新排序长代码。

这基本上完全符合您的要求:做一个临时的地方来放置您知道肯定不会与其他任何东西发生冲突的东西,并且您负责在您去的时候清理里面的东西。

是的,它很丑陋,而且可能令人气馁——您将被指示将您的工作分解为一组更小、更可重用的函数。

于 2019-10-30T01:35:26.877 回答
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/

于 2021-06-10T17:17:35.480 回答
0

正如其他人所建议的那样,在不污染封闭命名空间的情况下执行代码的 python 方法是将其放入类或函数中。这提出了一个轻微且通常无害的问题:定义函数将其名称放在封闭的命名空间中。如果这对您造成伤害,您可以使用 Python 的常规临时变量“_”命名您的函数:

def _():
    polluting_variable = foo()
    ...
_() # Run the code before something overwrites the variable.

这可以递归地完成,因为每个本地定义都从封闭范围中屏蔽了定义。

这种事情应该只在非常特殊的情况下才需要。一个有用的例子是使用 Databricks 的%run魔法,它在当前笔记本的全局范围内执行另一个笔记本的内容。将子笔记本的命令包装在临时函数中可以防止它们污染全局命名空间。

于 2019-12-18T20:22:54.883 回答