标签归档:scope

从类定义中的列表理解访问类变量

问题:从类定义中的列表理解访问类变量

如何从类定义中的列表理解中访问其他类变量?以下内容在Python 2中有效,但在Python 3中失败:

class Foo:
    x = 5
    y = [x for i in range(1)]

Python 3.2给出了错误:

NameError: global name 'x' is not defined

尝试Foo.x也不起作用。关于如何在Python 3中执行此操作的任何想法?

一个更复杂的激励示例:

from collections import namedtuple
class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for args in [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ]]

在此示例中,apply()这是一个不错的解决方法,但不幸的是它已从Python 3中删除。

How do you access other class variables from a list comprehension within the class definition? The following works in Python 2 but fails in Python 3:

class Foo:
    x = 5
    y = [x for i in range(1)]

Python 3.2 gives the error:

NameError: global name 'x' is not defined

Trying Foo.x doesn’t work either. Any ideas on how to do this in Python 3?

A slightly more complicated motivating example:

from collections import namedtuple
class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for args in [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ]]

In this example, apply() would have been a decent workaround, but it is sadly removed from Python 3.


回答 0

类范围和列表,集合或字典的理解以及生成器表达式不混合。

为什么;或者,关于这个的正式词

在Python 3中,为列表理解赋予了它们自己的适当范围(本地命名空间),以防止其局部变量渗入周围的范围内(即使在理解范围之后,也请参阅Python列表理解重新绑定名称。对吗?)。在模块或函数中使用这样的列表理解时,这很好,但是在类中,作用域范围有点奇怪

pep 227中对此进行了记录:

类范围内的名称不可访问。名称在最里面的函数范围内解析。如果类定义出现在嵌套作用域链中,则解析过程将跳过类定义。

并在 class复合语句文档中

然后,使用新创建的本地命名空间和原始的全局命名空间,在新的执行框架中执行该类的套件(请参见Naming and binding部分)。(通常,套件仅包含函数定义。)当类的套件完成执行时,其执行框架将被丢弃,但其本地命名空间将被保存[4]然后,使用基类的继承列表和属性字典的已保存本地命名空间创建类对象。

强调我的;执行框架是临时范围。

由于范围被重新用作类对象的属性,因此允许将其用作非本地范围也将导致未定义的行为。例如,如果一个类方法称为x嵌套作用域变量,然后又进行操作Foo.x,会发生什么情况?更重要的是,这对于Foo?Python 必须以不同的方式对待类范围,因为它与函数范围有很大不同。

最后但同样重要的是,链接 执行模型文档中命名和绑定部分明确提到了类作用域:

在类块中定义的名称范围仅限于该类块。它不会扩展到方法的代码块–包括理解和生成器表达式,因为它们是使用函数范围实现的。这意味着以下操作将失败:

class A:
     a = 42
     b = list(a + i for i in range(10))

因此,总结一下:您不能从函数,列出的理解或包含在该范围内的生成器表达式中访问类范围;它们的作用就好像该范围不存在。在Python 2中,列表理解是使用快捷方式实现的,但是在Python 3中,它们具有自己的功能范围(应该一直如此),因此您的示例中断了。无论Python版本如何,其他理解类型都有其自己的范围,因此具有set或dict理解的类似示例将在Python 2中中断。

# Same error, in Python 2 or 3
y = {x: x for i in range(1)}

(小)异常;或者,为什么一部分仍然可以工作

无论Python版本如何,理解或生成器表达式的一部分都在周围的范围内执行。那就是最外层可迭代的表达。在您的示例中,它是range(1)

y = [x for i in range(1)]
#               ^^^^^^^^

因此,使用 x在该表达式中不会引发错误:

# Runs fine
y = [i for i in range(x)]

这仅适用于最外面的可迭代对象。如果一个理解具有多个for子句,则内部的可迭代for子句在该理解的范围进行评估:

# NameError
y = [i for i in range(1) for j in range(x)]

做出此设计决定是为了在genexp创建时引发错误,而不是在创建生成器表达式的最外层可迭代器引发错误时,或者当最外层可迭代器变得不可迭代时,在迭代时抛出错误。理解共享此行为以保持一致性。

在引擎盖下看;或者,比您想要的方式更详细

您可以使用dis模块查看所有这些操作。在以下示例中,我将使用Python 3.3,因为它添加了合格的名称,这些名称可以整洁地标识我们要检查的代码对象。产生的字节码在其他方面与Python 3.2相同。

为了创建一个类,Python本质上采用了构成类主体的整个套件(因此所有内容都比该class <name>:行缩进了一层),并像执行一个函数一样执行:

>>> import dis
>>> def foo():
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo)
  2           0 LOAD_BUILD_CLASS     
              1 LOAD_CONST               1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>) 
              4 LOAD_CONST               2 ('Foo') 
              7 MAKE_FUNCTION            0 
             10 LOAD_CONST               2 ('Foo') 
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             16 STORE_FAST               0 (Foo) 

  5          19 LOAD_FAST                0 (Foo) 
             22 RETURN_VALUE         

首先LOAD_CONSTFoo该类中为类主体加载一个代码对象,然后将其放入函数中并进行调用。然后,该调用的结果用于创建类的命名空间,__dict__。到目前为止,一切都很好。

这里要注意的是字节码包含一个嵌套的代码对象。在Python中,类定义,函数,理解和生成器均表示为代码对象,这些对象不仅包含字节码,而且还包含表示局部变量,常量,取自全局变量的变量和取自嵌套作用域的变量的结构。编译后的字节码引用了这些结构,而python解释器知道如何访问给定的字节码。

这里要记住的重要一点是,Python在编译时创建了这些结构。该class套件是<code object Foo at 0x10a436030, file "<stdin>", line 2>已编译的代码对象()。

让我们检查创建类主体本身的代码对象。代码对象具有以下co_consts结构:

>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
  2           0 LOAD_FAST                0 (__locals__) 
              3 STORE_LOCALS         
              4 LOAD_NAME                0 (__name__) 
              7 STORE_NAME               1 (__module__) 
             10 LOAD_CONST               0 ('foo.<locals>.Foo') 
             13 STORE_NAME               2 (__qualname__) 

  3          16 LOAD_CONST               1 (5) 
             19 STORE_NAME               3 (x) 

  4          22 LOAD_CONST               2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>) 
             25 LOAD_CONST               3 ('foo.<locals>.Foo.<listcomp>') 
             28 MAKE_FUNCTION            0 
             31 LOAD_NAME                4 (range) 
             34 LOAD_CONST               4 (1) 
             37 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             40 GET_ITER             
             41 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             44 STORE_NAME               5 (y) 
             47 LOAD_CONST               5 (None) 
             50 RETURN_VALUE         

上面的字节码创建了类主体。该功能被执行并且将所得locals()的命名空间,包含xy用于创建类(不同之处在于因为它不工作x不被定义为一个全局)。请注意,在中存储5x,它会加载另一个代码对象。那就是列表理解;它像类主体一样被包装在一个函数对象中;创建的函数带有一个位置参数,该参数range(1)可迭代用于其循环代码,并转换为迭代器。如字节码所示,range(1)在类范围内进行评估。

从中可以看出,用于函数或生成器的代码对象与用于理解的代码对象之间的唯一区别是,后者在执行父代码对象时立即执行;字节码只是简单地动态创建一个函数,然后只需几个小步骤就可以执行它。

Python 2.x在那里改用内联字节码,这是Python 2.7的输出:

  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_CONST               0 (5)
              9 STORE_NAME               2 (x)

  4          12 BUILD_LIST               0
             15 LOAD_NAME                3 (range)
             18 LOAD_CONST               1 (1)
             21 CALL_FUNCTION            1
             24 GET_ITER            
        >>   25 FOR_ITER                12 (to 40)
             28 STORE_NAME               4 (i)
             31 LOAD_NAME                2 (x)
             34 LIST_APPEND              2
             37 JUMP_ABSOLUTE           25
        >>   40 STORE_NAME               5 (y)
             43 LOAD_LOCALS         
             44 RETURN_VALUE        

没有代码对象被加载,而是FOR_ITER循环内联运行。因此,在Python 3.x中,为列表生成器提供了自己的适当代码对象,这意味着它具有自己的作用域。

然而,理解与当模块或脚本首先被解释加载的Python源代码的其余部分一起编译,编译器并没有考虑一类套件的有效范围。在列表理解任何引用变量必须在查找范围周围的类定义,递归。如果编译器未找到该变量,则将其标记为全局变量。列表理解代码对象的反汇编显示x确实确实是作为全局加载的:

>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
  4           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_GLOBAL              0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

此字节代码块加载传入的第一个参数( range(1)迭代器),就像Python 2.x版本用于对其FOR_ITER进行循环并创建其输出一样。

如果我们xfoo函数中定义,x它将是一个单元格变量(单元格是指嵌套作用域):

>>> def foo():
...     x = 2
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
  5           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_DEREF               0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

LOAD_DEREF将间接加载x从代码对象小区对象:

>>> foo.__code__.co_cellvars               # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars  # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars  # Refers to `x` in foo
('x',)
>>> foo().y
[2]

实际引用从当前帧数据结构中查找值,当前帧数据结构是从功能对象的.__closure__属性初始化的。由于为理解代码对象创建的函数被再次丢弃,因此我们无法检查该函数的关闭情况。要查看实际的闭包,我们必须检查一个嵌套函数:

>>> def spam(x):
...     def eggs():
...         return x
...     return eggs
... 
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5

因此,总结一下:

  • 列表推导在Python 3中获得了自己的代码对象,并且函数,生成器或推导的代码对象之间没有区别。理解代码对象包装在一个临时函数对象中,并立即调用。
  • 代码对象是在编译时创建的,并且根据代码的嵌套作用域,将任何非局部变量标记为全局变量或自由变量。类主体被视为查找那些变量的范围。
  • 执行代码时,Python只需查看全局变量或当前正在执行的对象的关闭。由于编译器未将类主体作为范围包含在内,因此不考虑临时函数命名空间。

解决方法;或者,该怎么办

如果要x像在函数中那样为变量创建显式作用域,则可以将类作用域变量用于列表理解:

>>> class Foo:
...     x = 5
...     def y(x):
...         return [x for i in range(1)]
...     y = y(x)
... 
>>> Foo.y
[5]

y可以直接调用“临时” 功能。我们用它的返回值替换它。解决时考虑其范围x

>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)

当然,人们在阅读您的代码时会对此有些挠头。您可能要在其中添加一个大的粗注,以解释您为什么这样做。

最好的解决方法是仅使用__init__创建一个实例变量:

def __init__(self):
    self.y = [self.x for i in range(1)]

并避免一切费力的工作,并避免提出自己的问题。对于您自己的具体示例,我什至不将其存储namedtuple在类中。直接使用输出(根本不存储生成的类),或使用全局变量:

from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])

class StateDatabase:
    db = [State(*args) for args in [
       ('Alabama', 'Montgomery'),
       ('Alaska', 'Juneau'),
       # ...
    ]]

Class scope and list, set or dictionary comprehensions, as well as generator expressions do not mix.

The why; or, the official word on this

In Python 3, list comprehensions were given a proper scope (local namespace) of their own, to prevent their local variables bleeding over into the surrounding scope (see Python list comprehension rebind names even after scope of comprehension. Is this right?). That’s great when using such a list comprehension in a module or in a function, but in classes, scoping is a little, uhm, strange.

This is documented in pep 227:

Names in class scope are not accessible. Names are resolved in the innermost enclosing function scope. If a class definition occurs in a chain of nested scopes, the resolution process skips class definitions.

and in the class compound statement documentation:

The class’s suite is then executed in a new execution frame (see section Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains only function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved. [4] A class object is then created using the inheritance list for the base classes and the saved local namespace for the attribute dictionary.

Emphasis mine; the execution frame is the temporary scope.

Because the scope is repurposed as the attributes on a class object, allowing it to be used as a nonlocal scope as well leads to undefined behaviour; what would happen if a class method referred to x as a nested scope variable, then manipulates Foo.x as well, for example? More importantly, what would that mean for subclasses of Foo? Python has to treat a class scope differently as it is very different from a function scope.

Last, but definitely not least, the linked Naming and binding section in the Execution model documentation mentions class scopes explicitly:

The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail:

class A:
     a = 42
     b = list(a + i for i in range(10))

So, to summarize: you cannot access the class scope from functions, list comprehensions or generator expressions enclosed in that scope; they act as if that scope does not exist. In Python 2, list comprehensions were implemented using a shortcut, but in Python 3 they got their own function scope (as they should have had all along) and thus your example breaks. Other comprehension types have their own scope regardless of Python version, so a similar example with a set or dict comprehension would break in Python 2.

# Same error, in Python 2 or 3
y = {x: x for i in range(1)}

The (small) exception; or, why one part may still work

There’s one part of a comprehension or generator expression that executes in the surrounding scope, regardless of Python version. That would be the expression for the outermost iterable. In your example, it’s the range(1):

y = [x for i in range(1)]
#               ^^^^^^^^

Thus, using x in that expression would not throw an error:

# Runs fine
y = [i for i in range(x)]

This only applies to the outermost iterable; if a comprehension has multiple for clauses, the iterables for inner for clauses are evaluated in the comprehension’s scope:

# NameError
y = [i for i in range(1) for j in range(x)]

This design decision was made in order to throw an error at genexp creation time instead of iteration time when creating the outermost iterable of a generator expression throws an error, or when the outermost iterable turns out not to be iterable. Comprehensions share this behavior for consistency.

Looking under the hood; or, way more detail than you ever wanted

You can see this all in action using the dis module. I’m using Python 3.3 in the following examples, because it adds qualified names that neatly identify the code objects we want to inspect. The bytecode produced is otherwise functionally identical to Python 3.2.

To create a class, Python essentially takes the whole suite that makes up the class body (so everything indented one level deeper than the class <name>: line), and executes that as if it were a function:

>>> import dis
>>> def foo():
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo)
  2           0 LOAD_BUILD_CLASS     
              1 LOAD_CONST               1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>) 
              4 LOAD_CONST               2 ('Foo') 
              7 MAKE_FUNCTION            0 
             10 LOAD_CONST               2 ('Foo') 
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             16 STORE_FAST               0 (Foo) 

  5          19 LOAD_FAST                0 (Foo) 
             22 RETURN_VALUE         

The first LOAD_CONST there loads a code object for the Foo class body, then makes that into a function, and calls it. The result of that call is then used to create the namespace of the class, its __dict__. So far so good.

The thing to note here is that the bytecode contains a nested code object; in Python, class definitions, functions, comprehensions and generators all are represented as code objects that contain not only bytecode, but also structures that represent local variables, constants, variables taken from globals, and variables taken from the nested scope. The compiled bytecode refers to those structures and the python interpreter knows how to access those given the bytecodes presented.

The important thing to remember here is that Python creates these structures at compile time; the class suite is a code object (<code object Foo at 0x10a436030, file "<stdin>", line 2>) that is already compiled.

Let’s inspect that code object that creates the class body itself; code objects have a co_consts structure:

>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
  2           0 LOAD_FAST                0 (__locals__) 
              3 STORE_LOCALS         
              4 LOAD_NAME                0 (__name__) 
              7 STORE_NAME               1 (__module__) 
             10 LOAD_CONST               0 ('foo.<locals>.Foo') 
             13 STORE_NAME               2 (__qualname__) 

  3          16 LOAD_CONST               1 (5) 
             19 STORE_NAME               3 (x) 

  4          22 LOAD_CONST               2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>) 
             25 LOAD_CONST               3 ('foo.<locals>.Foo.<listcomp>') 
             28 MAKE_FUNCTION            0 
             31 LOAD_NAME                4 (range) 
             34 LOAD_CONST               4 (1) 
             37 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             40 GET_ITER             
             41 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             44 STORE_NAME               5 (y) 
             47 LOAD_CONST               5 (None) 
             50 RETURN_VALUE         

The above bytecode creates the class body. The function is executed and the resulting locals() namespace, containing x and y is used to create the class (except that it doesn’t work because x isn’t defined as a global). Note that after storing 5 in x, it loads another code object; that’s the list comprehension; it is wrapped in a function object just like the class body was; the created function takes a positional argument, the range(1) iterable to use for its looping code, cast to an iterator. As shown in the bytecode, range(1) is evaluated in the class scope.

From this you can see that the only difference between a code object for a function or a generator, and a code object for a comprehension is that the latter is executed immediately when the parent code object is executed; the bytecode simply creates a function on the fly and executes it in a few small steps.

Python 2.x uses inline bytecode there instead, here is output from Python 2.7:

  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_CONST               0 (5)
              9 STORE_NAME               2 (x)

  4          12 BUILD_LIST               0
             15 LOAD_NAME                3 (range)
             18 LOAD_CONST               1 (1)
             21 CALL_FUNCTION            1
             24 GET_ITER            
        >>   25 FOR_ITER                12 (to 40)
             28 STORE_NAME               4 (i)
             31 LOAD_NAME                2 (x)
             34 LIST_APPEND              2
             37 JUMP_ABSOLUTE           25
        >>   40 STORE_NAME               5 (y)
             43 LOAD_LOCALS         
             44 RETURN_VALUE        

No code object is loaded, instead a FOR_ITER loop is run inline. So in Python 3.x, the list generator was given a proper code object of its own, which means it has its own scope.

However, the comprehension was compiled together with the rest of the python source code when the module or script was first loaded by the interpreter, and the compiler does not consider a class suite a valid scope. Any referenced variables in a list comprehension must look in the scope surrounding the class definition, recursively. If the variable wasn’t found by the compiler, it marks it as a global. Disassembly of the list comprehension code object shows that x is indeed loaded as a global:

>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
  4           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_GLOBAL              0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

This chunk of bytecode loads the first argument passed in (the range(1) iterator), and just like the Python 2.x version uses FOR_ITER to loop over it and create its output.

Had we defined x in the foo function instead, x would be a cell variable (cells refer to nested scopes):

>>> def foo():
...     x = 2
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
  5           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_DEREF               0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

The LOAD_DEREF will indirectly load x from the code object cell objects:

>>> foo.__code__.co_cellvars               # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars  # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars  # Refers to `x` in foo
('x',)
>>> foo().y
[2]

The actual referencing looks the value up from the current frame data structures, which were initialized from a function object’s .__closure__ attribute. Since the function created for the comprehension code object is discarded again, we do not get to inspect that function’s closure. To see a closure in action, we’d have to inspect a nested function instead:

>>> def spam(x):
...     def eggs():
...         return x
...     return eggs
... 
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5

So, to summarize:

  • List comprehensions get their own code objects in Python 3, and there is no difference between code objects for functions, generators or comprehensions; comprehension code objects are wrapped in a temporary function object and called immediately.
  • Code objects are created at compile time, and any non-local variables are marked as either global or as free variables, based on the nested scopes of the code. The class body is not considered a scope for looking up those variables.
  • When executing the code, Python has only to look into the globals, or the closure of the currently executing object. Since the compiler didn’t include the class body as a scope, the temporary function namespace is not considered.

A workaround; or, what to do about it

If you were to create an explicit scope for the x variable, like in a function, you can use class-scope variables for a list comprehension:

>>> class Foo:
...     x = 5
...     def y(x):
...         return [x for i in range(1)]
...     y = y(x)
... 
>>> Foo.y
[5]

The ‘temporary’ y function can be called directly; we replace it when we do with its return value. Its scope is considered when resolving x:

>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)

Of course, people reading your code will scratch their heads over this a little; you may want to put a big fat comment in there explaining why you are doing this.

The best work-around is to just use __init__ to create an instance variable instead:

def __init__(self):
    self.y = [self.x for i in range(1)]

and avoid all the head-scratching, and questions to explain yourself. For your own concrete example, I would not even store the namedtuple on the class; either use the output directly (don’t store the generated class at all), or use a global:

from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])

class StateDatabase:
    db = [State(*args) for args in [
       ('Alabama', 'Montgomery'),
       ('Alaska', 'Juneau'),
       # ...
    ]]

回答 1

我认为这是Python 3中的一个缺陷。我希望他们能够改变它。

旧方法(适用于2.7,适用NameError: name 'x' is not defined于3+):

class A:
    x = 4
    y = [x+i for i in range(1)]

注意:仅使用范围A.x将无法解决

新方式(适用于3+):

class A:
    x = 4
    y = (lambda x=x: [x+i for i in range(1)])()

因为语法太丑陋,所以我通常在构造函数中初始化所有类变量

In my opinion it is a flaw in Python 3. I hope they change it.

Old Way (works in 2.7, throws NameError: name 'x' is not defined in 3+):

class A:
    x = 4
    y = [x+i for i in range(1)]

NOTE: simply scoping it with A.x would not solve it

New Way (works in 3+):

class A:
    x = 4
    y = (lambda x=x: [x+i for i in range(1)])()

Because the syntax is so ugly I just initialize all my class variables in the constructor typically


回答 2

公认的答案提供了很好的信息,但这里似乎还有其他一些不足之处–列表理解和生成器表达式之间的差异。我玩过的一个演示:

class Foo:

    # A class-level variable.
    X = 10

    # I can use that variable to define another class-level variable.
    Y = sum((X, X))

    # Works in Python 2, but not 3.
    # In Python 3, list comprehensions were given their own scope.
    try:
        Z1 = sum([X for _ in range(3)])
    except NameError:
        Z1 = None

    # Fails in both.
    # Apparently, generator expressions (that's what the entire argument
    # to sum() is) did have their own scope even in Python 2.
    try:
        Z2 = sum(X for _ in range(3))
    except NameError:
        Z2 = None

    # Workaround: put the computation in lambda or def.
    compute_z3 = lambda val: sum(val for _ in range(3))

    # Then use that function.
    Z3 = compute_z3(X)

    # Also worth noting: here I can refer to XS in the for-part of the
    # generator expression (Z4 works), but I cannot refer to XS in the
    # inner-part of the generator expression (Z5 fails).
    XS = [15, 15, 15, 15]
    Z4 = sum(val for val in XS)
    try:
        Z5 = sum(XS[i] for i in range(len(XS)))
    except NameError:
        Z5 = None

print(Foo.Z1, Foo.Z2, Foo.Z3, Foo.Z4, Foo.Z5)

The accepted answer provides excellent information, but there appear to be a few other wrinkles here — differences between list comprehension and generator expressions. A demo that I played around with:

class Foo:

    # A class-level variable.
    X = 10

    # I can use that variable to define another class-level variable.
    Y = sum((X, X))

    # Works in Python 2, but not 3.
    # In Python 3, list comprehensions were given their own scope.
    try:
        Z1 = sum([X for _ in range(3)])
    except NameError:
        Z1 = None

    # Fails in both.
    # Apparently, generator expressions (that's what the entire argument
    # to sum() is) did have their own scope even in Python 2.
    try:
        Z2 = sum(X for _ in range(3))
    except NameError:
        Z2 = None

    # Workaround: put the computation in lambda or def.
    compute_z3 = lambda val: sum(val for _ in range(3))

    # Then use that function.
    Z3 = compute_z3(X)

    # Also worth noting: here I can refer to XS in the for-part of the
    # generator expression (Z4 works), but I cannot refer to XS in the
    # inner-part of the generator expression (Z5 fails).
    XS = [15, 15, 15, 15]
    Z4 = sum(val for val in XS)
    try:
        Z5 = sum(XS[i] for i in range(len(XS)))
    except NameError:
        Z5 = None

print(Foo.Z1, Foo.Z2, Foo.Z3, Foo.Z4, Foo.Z5)

回答 3

这是Python中的错误。宣传被认为等同于for循环,但是在类中却并非如此。至少在Python 3.6.6之前的版本中,在类中使用的理解中,在理解内部只能访问该理解外部的一个变量,并且必须将其用作最外层的迭代器。在功能上,此范围限制不适用。

为了说明为什么这是一个错误,让我们回到原始示例。这将失败:

class Foo:
    x = 5
    y = [x for i in range(1)]

但这有效:

def Foo():
    x = 5
    y = [x for i in range(1)]

该限制在参考指南的本节结尾处说明。

This is a bug in Python. Comprehensions are advertised as being equivalent to for loops, but this is not true in classes. At least up to Python 3.6.6, in a comprehension used in a class, only one variable from outside the comprehension is accessible inside the comprehension, and it must be used as the outermost iterator. In a function, this scope limitation does not apply.

To illustrate why this is a bug, let’s return to the original example. This fails:

class Foo:
    x = 5
    y = [x for i in range(1)]

But this works:

def Foo():
    x = 5
    y = [x for i in range(1)]

The limitation is stated at the end of this section in the reference guide.


回答 4

由于最外层的迭代器是在周围的范围内进行评估的,因此我们可以zip一起使用itertools.repeat将依赖项传递到理解范围内:

import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]

也可以for在理解中使用嵌套循环,并将依赖项包含在最外层的可迭代对象中:

class Foo:
    x = 5
    y = [j for j in (x,) for i in range(3)]

对于OP的特定示例:

from collections import namedtuple
import itertools as it

class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for State, args in zip(it.repeat(State), [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ])]

Since the outermost iterator is evaluated in the surrounding scope we can use zip together with itertools.repeat to carry the dependencies over to the comprehension’s scope:

import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]

One can also use nested for loops in the comprehension and include the dependencies in the outermost iterable:

class Foo:
    x = 5
    y = [j for j in (x,) for i in range(3)]

For the specific example of the OP:

from collections import namedtuple
import itertools as it

class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for State, args in zip(it.repeat(State), [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ])]

嵌套类的范围?

问题:嵌套类的范围?

我试图了解Python嵌套类中的作用域。这是我的示例代码:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

类的创建未完成,并且出现错误:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

尝试inner_var = Outerclass.outer_var不起作用。我得到:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

我正在尝试从访问静态outer_var信息InnerClass

有没有办法做到这一点?

I’m trying to understand scope in nested classes in Python. Here is my example code:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

The creation of class does not complete and I get the error:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Trying inner_var = Outerclass.outer_var doesn’t work. I get:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

I am trying to access the static outer_var from InnerClass.

Is there a way to do this?


回答 0

class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

这与其他语言中的类似功能不太一样,并且使用全局查找而不是限制对的访问outer_var。(如果更改名称Outer绑定到的对象,则此代码将在下次执行该对象时使用该对象。)

相反,如果您希望所有Inner对象都具有对的引用,Outer因为outer_var它实际上是实例属性:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

请注意,嵌套类在Python中并不常见,并且不会自动暗示类之间的任何特殊关系。您最好不要嵌套。(您仍然可以设置一个类属性上OuterInner,如果你想要的。)

class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

This isn’t quite the same as similar things work in other languages, and uses global lookup instead of scoping the access to outer_var. (If you change what object the name Outer is bound to, then this code will use that object the next time it is executed.)

If you instead want all Inner objects to have a reference to an Outer because outer_var is really an instance attribute:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

Note that nesting classes is somewhat uncommon in Python, and doesn’t automatically imply any sort of special relationship between the classes. You’re better off not nesting. (You can still set a class attribute on Outer to Inner, if you want.)


回答 1

我认为您可以做到:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

您遇到的问题是由于以下原因:

块是作为单元执行的一段Python程序文本。以下是块:模块,函数体和类定义。
(…)
范围定义了块中名称的可见性。
(…)
在类块中定义的名称范围仅限于该类块;它不会扩展到方法的代码块–包括生成器表达式,因为它们是使用函数范围实现的。这意味着以下操作将失败:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

上面的意思是:
一个函数体是一个代码块,一个方法是一个函数,那么在类定义中存在于该函数体之外的名称将不会扩展到该函数体。

用您的情况解释一下:
类定义是一个代码块,然后在外部类定义中存在的内部类定义之外定义的名称不会扩展到内部类定义。

I think you can simply do:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

The problem you encountered is due to this:

A block is a piece of Python program text that is executed as a unit. The following are blocks: a module, a function body, and a class definition.
(…)
A scope defines the visibility of a name within a block.
(…)
The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes generator expressions since they are implemented using a function scope. This means that the following will fail:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

The above means:
a function body is a code block and a method is a function, then names defined out of the function body present in a class definition do not extend to the function body.

Paraphrasing this for your case:
a class definition is a code block, then names defined out of the inner class definition present in an outer class definition do not extend to the inner class definition.


回答 2

如果您不使用嵌套类,则可能会更好。如果必须嵌套,请尝试以下操作:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

或在嵌套它们之前声明两个类:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(在此之后,您可以del InnerClass根据需要。)

You might be better off if you just don’t use nested classes. If you must nest, try this:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

Or declare both classes before nesting them:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(After this you can del InnerClass if you need to.)


回答 3

最简单的解决方案:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

它要求您保持明确,但不需要花费很多精力。

Easiest solution:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

It requires you to be explicit, but doesn’t take much effort.


回答 4

在Python中,可变对象作为引用传递,因此您可以将外部类的引用传递给内部类。

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)

In Python mutable objects are passed as reference, so you can pass a reference of the outer class to the inner class.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)

回答 5

所有说明都可以在Python文档中找到。

对于您的第一个错误<type 'exceptions.NameError'>: name 'outer_var' is not defined。解释是:

没有从方法内部引用数据属性(或其他方法!)的捷径。我发现这实际上提高了方法的可读性:浏览方法时,不会混淆局部变量和实例变量。

引自《 Python教程9.4》

对于第二个错误 <type 'exceptions.NameError'>: name 'OuterClass' is not defined

当正常保留类定义时(通过结尾),将创建一个类对象。

引自Python教程9.3.1

因此,当您尝试时inner_var = Outerclass.outer_varQuterclass尚未创建,这就是为什么name 'OuterClass' is not defined

有关第一个错误的更详细但乏味的解释:

尽管类可以访问封闭函数的作用域,但是它们不能充当嵌套在类内的代码的封闭作用域:Python在封闭函数中搜索引用的名称,但从不搜索任何封闭类。也就是说,一个类是一个局部作用域,可以访问封闭的局部作用域,但不能用作进一步嵌套代码的封闭的局部作用域。

引用自Learning.Python(5th).Mark.Lutz

All explanations can be found in Python Documentation The Python Tutorial

For your first error <type 'exceptions.NameError'>: name 'outer_var' is not defined. The explanation is:

There is no shorthand for referencing data attributes (or other methods!) from within methods. I find that this actually increases the readability of methods: there is no chance of confusing local variables and instance variables when glancing through a method.

quoted from The Python Tutorial 9.4

For your second error <type 'exceptions.NameError'>: name 'OuterClass' is not defined

When a class definition is left normally (via the end), a class object is created.

quoted from The Python Tutorial 9.3.1

So when you try inner_var = Outerclass.outer_var, the Quterclass hasn’t been created yet, that’s why name 'OuterClass' is not defined

A more detailed but tedious explanation for your first error:

Although classes have access to enclosing functions’ scopes, though, they do not act as enclosing scopes to code nested within the class: Python searches enclosing functions for referenced names, but never any enclosing classes. That is, a class is a local scope and has access to enclosing local scopes, but it does not serve as an enclosing local scope to further nested code.

quoted from Learning.Python(5th).Mark.Lutz


嵌套函数中的局部变量

问题:嵌套函数中的局部变量

好的,请耐心等待,我知道它看起来会令人费解,但是请帮助我了解发生了什么。

from functools import partial

class Cage(object):
    def __init__(self, animal):
        self.animal = animal

def gotimes(do_the_petting):
    do_the_petting()

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        cage = Cage(animal)

        def pet_function():
            print "Mary pets the " + cage.animal + "."

        yield (animal, partial(gotimes, pet_function))

funs = list(get_petters())

for name, f in funs:
    print name + ":", 
    f()

给出:

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

所以基本上,为什么我没有得到三种不同的动物?难道不是cage“打包”到嵌套函数的局部作用域中吗?如果不是,对嵌套函数的调用如何查找局部变量?

我知道遇到这些问题通常意味着一个人“做错了”,但是我想了解会发生什么。

Okay, bear with me on this, I know it’s going to look horribly convoluted, but please help me understand what’s happening.

from functools import partial

class Cage(object):
    def __init__(self, animal):
        self.animal = animal

def gotimes(do_the_petting):
    do_the_petting()

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        cage = Cage(animal)

        def pet_function():
            print "Mary pets the " + cage.animal + "."

        yield (animal, partial(gotimes, pet_function))

funs = list(get_petters())

for name, f in funs:
    print name + ":", 
    f()

Gives:

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

So basically, why am I not getting three different animals? Isn’t the cage ‘packaged’ into the local scope of the nested function? If not, how does a call to the nested function look up the local variables?

I know that running into these kind of problems usually means one is ‘doing it wrong’, but I’d like to understand what happens.


回答 0

嵌套函数在执行时(而不是在定义时)从父范围中查找变量。

编译函数主体,然后验证“自由”变量(未在函数本身中通过赋值定义),然后将其作为闭包单元绑定到函数,并且代码使用索引引用每个单元格。pet_function因此具有一个自由变量(cage),然后将其通过一个闭合单元引用,索引为0的闭合本身指向局部变量cageget_petters功能。

当您实际调用该函数时,该闭包将用于在您调用该函数时查看cage周围作用域中的值。问题就在这里。在您调用函数时,该函数已经完成了对其结果的计算。将在在执行过程中的一些点局部变量分配各的,和字符串,但在功能的结束,包含了最后一个值。因此,当您调用每个动态返回的函数时,就会得到打印的值。get_petterscage'cow''dog''cat'cage'cat''cat'

解决方法是不依赖闭包。您可以改用部分函数,创建新的函数作用域或将变量绑定为关键字parameter默认值

  • 部分函数示例,使用functools.partial()

    from functools import partial
    
    def pet_function(cage=None):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, partial(pet_function, cage=cage)))
  • 创建一个新的范围示例:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
  • 将变量绑定为关键字参数的默认值:

    def pet_function(cage=cage):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, pet_function))

无需scoped_cage在循环中定义函数,编译仅进行一次,而不是在循环的每次迭代中进行。

The nested function looks up variables from the parent scope when executed, not when defined.

The function body is compiled, and the ‘free’ variables (not defined in the function itself by assignment), are verified, then bound as closure cells to the function, with the code using an index to reference each cell. pet_function thus has one free variable (cage) which is then referenced via a closure cell, index 0. The closure itself points to the local variable cage in the get_petters function.

When you actually call the function, that closure is then used to look at the value of cage in the surrounding scope at the time you call the function. Here lies the problem. By the time you call your functions, the get_petters function is already done computing it’s results. The cage local variable at some point during that execution was assigned each of the 'cow', 'dog', and 'cat' strings, but at the end of the function, cage contains that last value 'cat'. Thus, when you call each of the dynamically returned functions, you get the value 'cat' printed.

The work-around is to not rely on closures. You can use a partial function instead, create a new function scope, or bind the variable as a default value for a keyword parameter.

  • Partial function example, using functools.partial():

    from functools import partial
    
    def pet_function(cage=None):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, partial(pet_function, cage=cage)))
    
  • Creating a new scope example:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
    
  • Binding the variable as a default value for a keyword parameter:

    def pet_function(cage=cage):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, pet_function))
    

There is no need to define the scoped_cage function in the loop, compilation only takes place once, not on each iteration of the loop.


回答 1

我的理解是,在实际调用产生的pet_function时而不是之前,在父函数命名空间中查找了笼子。

所以当你这样做

funs = list(get_petters())

您生成3个函数,这些函数将找到最后创建的笼子。

如果您将最后一个循环替换为:

for name, f in get_petters():
    print name + ":", 
    f()

您实际上会得到:

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

My understanding is that cage is looked for in the parent function namespace when the yielded pet_function is actually called, not before.

So when you do

funs = list(get_petters())

You generate 3 functions which will find the lastly created cage.

If you replace your last loop with :

for name, f in get_petters():
    print name + ":", 
    f()

You will actually get :

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

回答 2

这源于以下

for i in range(2): 
    pass

print(i)  # prints 1

迭代后,将的值i延迟存储为最终值。

作为生成器,该函数可以工作(即依次打印每个值),但是在转换为列表时,它将在生成器上运行,因此对cagecage.animal)的所有调用都返回cats。

This stems from the following

for i in range(2): 
    pass

print(i)  # prints 1

after iterating the value of i is lazily stored as its final value.

As a generator the function would work (i.e. printing each value in turn), but when transforming to a list it runs over the generator, hence all calls to cage (cage.animal) return cats.


回答 3

让我们简化问题。定义:

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        def pet_function():
            return "Mary pets the " + animal + "."

        yield (animal, pet_function)

然后,就像在问题中一样,我们得到:

>>> for name, f in list(get_petters()):
...     print(name + ":", f())

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

但是,如果我们避免创建list()第一个:

>>> for name, f in get_petters():
...     print(name + ":", f())

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

这是怎么回事?为什么这种微妙的差异会完全改变我们的结果?


如果我们看一下list(get_petters()),从不断变化的内存地址可以明显看出,我们确实产生了三种不同的功能:

>>> list(get_petters())

[('cow', <function get_petters.<locals>.pet_function at 0x7ff2b988d790>),
 ('dog', <function get_petters.<locals>.pet_function at 0x7ff2c18f51f0>),
 ('cat', <function get_petters.<locals>.pet_function at 0x7ff2c14a9f70>)]

但是,请看一下cell这些函数绑定到的:

>>> for _, f in list(get_petters()):
...     print(f(), f.__closure__)

Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)

>>> for _, f in get_petters():
...     print(f(), f.__closure__)

Mary pets the cow. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a95670>,)
Mary pets the dog. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a952f0>,)
Mary pets the cat. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c3f437f0>,)

对于这两个循环,cell对象在整个迭代过程中保持不变。但是,正如预期的那样,str它引用的具体内容在第二个循环中有所不同。该cell对象引用animal,在get_petters()调用时创建。但是,在生成器函数运行时animal更改str它所指的对象。

在第一个循环中,在每次迭代期间,我们都创建了所有fs,但是只有在生成器get_petters()完全用尽并且list已经创建a 函数之后,才调用它们。

在第二个循环中,在每次迭代期间,我们暂停get_petters()生成器并f在每次暂停后调用。因此,我们最终animal在生成器功能暂停的那一刻检索了值。

正如@Claudiu对类似问题的回答

创建了三个单独的函数,但是每个函数都封闭了定义它们的环境-在这种情况下,是全局环境(如果将循环放在另一个函数内部,则为外部函数的环境)。不过,这确实是问题所在-在这种环境中,animal变量是突变的,并且所有的闭包都引用相同的animal

[编者注:i已更改为animal。]

Let’s simplify the question. Define:

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        def pet_function():
            return "Mary pets the " + animal + "."

        yield (animal, pet_function)

Then, just like in the question, we get:

>>> for name, f in list(get_petters()):
...     print(name + ":", f())

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

But if we avoid creating a list() first:

>>> for name, f in get_petters():
...     print(name + ":", f())

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

What’s going on? Why does this subtle difference completely change our results?


If we look at list(get_petters()), it’s clear from the changing memory addresses that we do indeed yield three different functions:

>>> list(get_petters())

[('cow', <function get_petters.<locals>.pet_function at 0x7ff2b988d790>),
 ('dog', <function get_petters.<locals>.pet_function at 0x7ff2c18f51f0>),
 ('cat', <function get_petters.<locals>.pet_function at 0x7ff2c14a9f70>)]

However, take a look at the cells that these functions are bound to:

>>> for _, f in list(get_petters()):
...     print(f(), f.__closure__)

Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)

>>> for _, f in get_petters():
...     print(f(), f.__closure__)

Mary pets the cow. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a95670>,)
Mary pets the dog. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a952f0>,)
Mary pets the cat. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c3f437f0>,)

For both loops, the cell object remains the same throughout the iterations. However, as expected, the specific str it references varies in the second loop. The cell object refers to animal, which is created when get_petters() is called. However, animal changes what str object it refers to as the generator function runs.

In the first loop, during each iteration, we create all the fs, but we only call them after the generator get_petters() is completely exhausted and a list of functions is already created.

In the second loop, during each iteration, we are pausing the get_petters() generator and calling f after each pause. Thus, we end up retrieving the value of animal at that moment in time that the generator function is paused.

As @Claudiu puts in an answer to a similar question:

Three separate functions are created, but they each have the closure of the environment they’re defined in – in this case, the global environment (or the outer function’s environment if the loop is placed inside another function). This is exactly the problem, though — in this environment, animal is mutated, and the closures all refer to the same animal.

[Editor note: i has been changed to animal.]


如何从内部类访问外部类?

问题:如何从内部类访问外部类?

我有这样的情况

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.Outer.some_method()    # <-- this is the line in question

如何OuterInner类中访问类的方法?

I have a situation like so…

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.Outer.some_method()    # <-- this is the line in question

How can I access the Outer class’s method from the Inner class?


回答 0

嵌套类的方法不能直接访问外部类的实例属性。

请注意,即使您已经创建了内部类的实例,也不一定存在外部类的实例。

实际上,通常建议不要使用嵌套类,因为嵌套并不暗示内部类和外部类之间的任何特定关系。

The methods of a nested class cannot directly access the instance attributes of the outer class.

Note that it is not necessarily the case that an instance of the outer class exists even when you have created an instance of the inner class.

In fact, it is often recommended against using nested classes, since the nesting does not imply any particular relationship between the inner and outer classes.


回答 1

您正在尝试从内部类实例访问外部类的实例。因此,只需使用工厂方法构建内部实例并将外部实例传递给它即可。

class Outer(object):

    def createInner(self):
        return Outer.Inner(self)

    class Inner(object):
        def __init__(self, outer_instance):
            self.outer_instance = outer_instance
            self.outer_instance.somemethod()

        def inner_method(self):
            self.outer_instance.anothermethod()

You’re trying to access Outer’s class instance, from inner class instance. So just use factory-method to build Inner instance and pass Outer instance to it.

class Outer(object):

    def createInner(self):
        return Outer.Inner(self)

    class Inner(object):
        def __init__(self, outer_instance):
            self.outer_instance = outer_instance
            self.outer_instance.somemethod()

        def inner_method(self):
            self.outer_instance.anothermethod()

回答 2

也许我很生气,但这确实非常容易-事情是使您的内部类成为外部类的方法…

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

另外…“ self”仅按惯例使用,因此您可以执行以下操作:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

可能会导致您无法从外部类外部创建内部类的想法……但这不是事实:

class Bumblebee():

    def do_sthg( self ):
        print "sthg"

    def giveMeAnInnerClass( outerClassSelf ):

        class mooble():
            def do_sthg_different( self ):
                print "something diff\n"
                outerClassSelf.do_sthg()
        return mooble

然后,在几英里远的地方:

blob = Bumblebee().giveMeAnInnerClass()()
blob.do_sthg_different()    

甚至将船伸出一点并扩展此内部类(要使super()正常工作,您必须将mooble的类签名更改为“ class mooble(object)”

class InnerBumblebeeWithAddedBounce( Bumblebee().giveMeAnInnerClass() ):
    def bounce( self ):
        print "bounce"

    def do_sthg_different( self ):
        super( InnerBumblebeeWithAddedBounce, self ).do_sthg_different()
        print "and more different"


ibwab = InnerBumblebeeWithAddedBounce()    
ibwab.bounce()
ibwab.do_sthg_different()

后来

mrh1997提出了关于使用此技术传递的内部类的非公共继承的有趣观点。但似乎解决方案非常简单:

class Fatty():
    def do_sthg( self ):
        pass

    class InnerFatty( object ):
        pass

    def giveMeAnInnerFattyClass(self):
        class ExtendedInnerFatty( Fatty.InnerFatty ):
            pass
        return ExtendedInnerFatty

fatty1 = Fatty()
fatty2 = Fatty()

innerFattyClass1 = fatty1.giveMeAnInnerFattyClass()
innerFattyClass2 = fatty2.giveMeAnInnerFattyClass()

print ( issubclass( innerFattyClass1, Fatty.InnerFatty ))
print ( issubclass( innerFattyClass2, Fatty.InnerFatty ))

maybe I’m mad but this seems very easy indeed – the thing is to make your inner class inside a method of the outer class…

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Plus… “self” is only used by convention, so you could do this:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

It might be objected that you can’t then create this inner class from outside the outer class… but this ain’t true:

class Bumblebee():

    def do_sthg( self ):
        print "sthg"

    def giveMeAnInnerClass( outerClassSelf ):

        class mooble():
            def do_sthg_different( self ):
                print "something diff\n"
                outerClassSelf.do_sthg()
        return mooble

then, somewhere miles away:

blob = Bumblebee().giveMeAnInnerClass()()
blob.do_sthg_different()    

even push the boat out a bit and extend this inner class (NB to get super() to work you have to change the class signature of mooble to “class mooble( object )”

class InnerBumblebeeWithAddedBounce( Bumblebee().giveMeAnInnerClass() ):
    def bounce( self ):
        print "bounce"

    def do_sthg_different( self ):
        super( InnerBumblebeeWithAddedBounce, self ).do_sthg_different()
        print "and more different"


ibwab = InnerBumblebeeWithAddedBounce()    
ibwab.bounce()
ibwab.do_sthg_different()

later

mrh1997 raised an interesting point about the non-common inheritance of inner classes delivered using this technique. But it seems that the solution is pretty straightforward:

class Fatty():
    def do_sthg( self ):
        pass

    class InnerFatty( object ):
        pass

    def giveMeAnInnerFattyClass(self):
        class ExtendedInnerFatty( Fatty.InnerFatty ):
            pass
        return ExtendedInnerFatty

fatty1 = Fatty()
fatty2 = Fatty()

innerFattyClass1 = fatty1.giveMeAnInnerFattyClass()
innerFattyClass2 = fatty2.giveMeAnInnerFattyClass()

print ( issubclass( innerFattyClass1, Fatty.InnerFatty ))
print ( issubclass( innerFattyClass2, Fatty.InnerFatty ))

回答 3

您是要使用继承,而不是像这样嵌套类吗?您所做的事情在Python中并没有多大意义。

您可以Outer通过仅Outer.some_method在内部类的方法中进行引用来访问’some_method ,但是它不会按预期的那样工作。例如,如果您尝试这样做:

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            Outer.some_method()

…初始化Inner对象时会收到TypeError ,因为Outer.some_method期望接收Outer实例作为其第一个参数。(在上面的示例中,您基本上是在尝试some_method作为的类方法进行调用Outer。)

Do you mean to use inheritance, rather than nesting classes like this? What you’re doing doesn’t make a heap of sense in Python.

You can access the Outer‘s some_method by just referencing Outer.some_method within the inner class’s methods, but it’s not going to work as you expect it will. For example, if you try this:

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            Outer.some_method()

…you’ll get a TypeError when initialising an Inner object, because Outer.some_method expects to receive an Outer instance as its first argument. (In the example above, you’re basically trying to call some_method as a class method of Outer.)


回答 4

您可以使用元类轻松访问外部类:创建外部类后,检查任何类的属性dict(或应用所需的任何逻辑-我的例子很简单)并设置相应的值:

import six
import inspect


# helper method from `peewee` project to add metaclass
_METACLASS_ = '_metaclass_helper_'
def with_metaclass(meta, base=object):
    return meta(_METACLASS_, (base,), {})


class OuterMeta(type):
    def __new__(mcs, name, parents, dct):
        cls = super(OuterMeta, mcs).__new__(mcs, name, parents, dct)
        for klass in dct.values():
            if inspect.isclass(klass):
                print("Setting outer of '%s' to '%s'" % (klass, cls))
                klass.outer = cls

        return cls


# @six.add_metaclass(OuterMeta) -- this is alternative to `with_metaclass`
class Outer(with_metaclass(OuterMeta)):
    def foo(self):
        return "I'm outer class!"

    class Inner(object):
        outer = None  # <-- by default it's None

        def bar(self):
            return "I'm inner class"


print(Outer.Inner.outer)
>>> <class '__main__.Outer'>
assert isinstance(Outer.Inner.outer(), Outer)

print(Outer().foo())
>>> I'm outer class!
print(Outer.Inner.outer().foo())
>>> I'm outer class!
print(Outer.Inner().outer().foo())
>>> I'm outer class!
print(Outer.Inner().bar())
>>> I'm inner class!

使用这种方法,您可以轻松地相互绑定和引用两个类。

You can easily access to outer class using metaclass: after creation of outer class check it’s attribute dict for any classes (or apply any logic you need – mine is just trivial example) and set corresponding values:

import six
import inspect


# helper method from `peewee` project to add metaclass
_METACLASS_ = '_metaclass_helper_'
def with_metaclass(meta, base=object):
    return meta(_METACLASS_, (base,), {})


class OuterMeta(type):
    def __new__(mcs, name, parents, dct):
        cls = super(OuterMeta, mcs).__new__(mcs, name, parents, dct)
        for klass in dct.values():
            if inspect.isclass(klass):
                print("Setting outer of '%s' to '%s'" % (klass, cls))
                klass.outer = cls

        return cls


# @six.add_metaclass(OuterMeta) -- this is alternative to `with_metaclass`
class Outer(with_metaclass(OuterMeta)):
    def foo(self):
        return "I'm outer class!"

    class Inner(object):
        outer = None  # <-- by default it's None

        def bar(self):
            return "I'm inner class"


print(Outer.Inner.outer)
>>> <class '__main__.Outer'>
assert isinstance(Outer.Inner.outer(), Outer)

print(Outer().foo())
>>> I'm outer class!
print(Outer.Inner.outer().foo())
>>> I'm outer class!
print(Outer.Inner().outer().foo())
>>> I'm outer class!
print(Outer.Inner().bar())
>>> I'm inner class!

Using this approach, you can easily bind and refer two classes between each other.


回答 5

基于这个问题的另一个答案,我创建了一些Python代码来使用其内部类中外部类。我认为它简短,简单且易于理解。

class higher_level__unknown_irrelevant_name__class:
    def __init__(self, ...args...):
        ...other code...
        # Important lines to access sub-classes.
        subclasses = self._subclass_container()
        self.some_subclass = subclasses["some_subclass"]
        del subclasses # Free up variable for other use.

    def sub_function(self, ...args...):
        ...other code...

    def _subclass_container(self):
        _parent_class = self # Create access to parent class.
        class some_subclass:
            def __init__(self):
                self._parent_class = _parent_class # Easy access from self.
                # Optional line, clears variable space, but SHOULD NOT BE USED
                # IF THERE ARE MULTIPLE SUBCLASSES as would stop their parent access.
                #  del _parent_class
        class subclass_2:
            def __init__(self):
                self._parent_class = _parent_class
        # Return reference(s) to the subclass(es).
        return {"some_subclass": some_subclass, "subclass_2": subclass_2}

主代码为“生产就绪”(无注释等)。切记将尖括号(例如<x>)中的每个值全部替换为所需值。

class <higher_level_class>:
    def __init__(self):
        subclasses = self._subclass_container()
        self.<sub_class> = subclasses[<sub_class, type string>]
        del subclasses

    def _subclass_container(self):
        _parent_class = self
        class <sub_class>:
            def __init__(self):
                self._parent_class = _parent_class
        return {<sub_class, type string>: <sub_class>}

有关此方法如何工作的说明(基本步骤):

  1. 创建一个命名_subclass_container为充当包装器的函数,以访问变量self(对高层类的引用)(从在函数内部运行的代码)。

    1. 创建一个名为_parent_class的变量self,该变量引用此函数的变量,子类_subclass_container可以访问该变量(避免名称与self子类中的其他变量发生冲突)。

    2. 将子类/子类作为字典/列表返回,以便调用该_subclass_container函数的代码可以访问内部的子类。

  2. __init__更高级别的类(或其他需要的地方)中的函数中,将返回的子类从函数接收_subclass_container到变量中subclasses

  3. 将存储在subclasses变量中的子类分配给更高级别的类的属性。

一些使场景更容易的提示:

使将子类分配给更高级别的类的代码更易于复制,并在从其 功能发生了变化的更高级别的类派生的类中使用__init__

在主代码的第12行之前插入:

def _subclass_init(self):

然后将(主代码的)第5-6行插入此功能,并用以下代码替换第4-7行:

self._subclass_init(self)

当存在大量/未知数量的子类时,可以将子类分配给更高级别的类。

用以下代码替换第6行:

for subclass_name in list(subclasses.keys()):
    setattr(self, subclass_name, subclasses[subclass_name])

该解决方案将是有用的,并且应该无法获得更高级别的类名的示例场景:

创建一个名为“ a”(class a:)的类。它具有需要访问它的子类(父类)。一个子类称为“ x1”。在此子类中,将a.run_func()运行代码。

然后,从类“ a”()派生另一个名为“ b”的类class b(a):。之后,将运行一些代码b.x1()(调用b的子函数“ x1”(派生的子类))。该函数运行a.run_func(),调用类“ a” 的函数“ run_func ”,而不是其父级“ b”的函数“ run_func”(应如此),因为在类“ a”中定义的函数被设置为引用类“ a”的功能,因为它是其父级。

这将导致问题(例如,如果函数a.run_func已被删除),并且不重写类中代码的唯一解决方案a.x1将是x1使用从类“ a”派生的所有类的更新代码重新定义子类,这显然是困难且不值得的它。

I’ve created some Python code to use an outer class from its inner class, based on a good idea from another answer for this question. I think it’s short, simple and easy to understand.

class higher_level__unknown_irrelevant_name__class:
    def __init__(self, ...args...):
        ...other code...
        # Important lines to access sub-classes.
        subclasses = self._subclass_container()
        self.some_subclass = subclasses["some_subclass"]
        del subclasses # Free up variable for other use.

    def sub_function(self, ...args...):
        ...other code...

    def _subclass_container(self):
        _parent_class = self # Create access to parent class.
        class some_subclass:
            def __init__(self):
                self._parent_class = _parent_class # Easy access from self.
                # Optional line, clears variable space, but SHOULD NOT BE USED
                # IF THERE ARE MULTIPLE SUBCLASSES as would stop their parent access.
                #  del _parent_class
        class subclass_2:
            def __init__(self):
                self._parent_class = _parent_class
        # Return reference(s) to the subclass(es).
        return {"some_subclass": some_subclass, "subclass_2": subclass_2}

The main code, “production ready” (without comments, etc.). Remember to replace all of each value in angle brackets (e.g. <x>) with the desired value.

class <higher_level_class>:
    def __init__(self):
        subclasses = self._subclass_container()
        self.<sub_class> = subclasses[<sub_class, type string>]
        del subclasses

    def _subclass_container(self):
        _parent_class = self
        class <sub_class>:
            def __init__(self):
                self._parent_class = _parent_class
        return {<sub_class, type string>: <sub_class>}

Explanation of how this method works (the basic steps):

  1. Create a function named _subclass_container to act as a wrapper to access the variable self, a reference to the higher level class (from code running inside the function).

    1. Create a variable named _parent_class which is a reference to the variable self of this function, that the sub-classes of _subclass_container can access (avoids name conflicts with other self variables in subclasses).

    2. Return the sub-class/sub-classes as a dictionary/list so code calling the _subclass_container function can access the sub-classes inside.

  2. In the __init__ function inside the higher level class (or wherever else needed), receive the returned sub-classes from the function _subclass_container into the variable subclasses.

  3. Assign sub-classes stored in the subclasses variable to attributes of the higher level class.

A few tips to make scenarios easier:

Making the code to assign the sub classes to the higher level class easier to copy and be used in classes derived from the higher level class that have their __init__ function changed:

Insert before line 12 in the main code:

def _subclass_init(self):

Then insert into this function lines 5-6 (of the main code) and replace lines 4-7 with the following code:

self._subclass_init(self)

Making subclass assigning to the higher level class possible when there are many/unknown quantities of subclasses.

Replace line 6 with the following code:

for subclass_name in list(subclasses.keys()):
    setattr(self, subclass_name, subclasses[subclass_name])

Example scenario of where this solution would be useful and where the higher level class name should be impossible to get:

A class, named “a” (class a:) is created. It has subclasses that need to access it (the parent). One subclass is called “x1”. In this subclass, the code a.run_func() is run.

Then another class, named “b” is created, derived from class “a” (class b(a):). After that, some code runs b.x1() (calling the sub function “x1” of b, a derived sub-class). This function runs a.run_func(), calling the function “run_func” of class “a”, not the function “run_func” of its parent, “b” (as it should), because the function which was defined in class “a” is set to refer to the function of class “a”, as that was its parent.

This would cause problems (e.g. if function a.run_func has been deleted) and the only solution without rewriting the code in class a.x1 would be to redefine the sub-class x1 with updated code for all classes derived from class “a” which would obviously be difficult and not worth it.


回答 6

我发现了这个

调整了适合您的问题:

class Outer(object):
    def some_method(self):
        # do something

    class _Inner(object):
        def __init__(self, outer):
            outer.some_method()
    def Inner(self):
        return _Inner(self)

我确定您可以以某种方式为此目的编写装饰器

相关:python内部类的目的是什么?

I found this.

Tweaked to suite your question:

class Outer(object):
    def some_method(self):
        # do something

    class _Inner(object):
        def __init__(self, outer):
            outer.some_method()
    def Inner(self):
        return _Inner(self)

I’m sure you can somehow write a decorator for this or something

related: What is the purpose of python’s inner classes?


回答 7

另一种可能性:

class _Outer (object):
    # Define your static methods here, e.g.
    @staticmethod
    def subclassRef ():
        return Outer

class Outer (_Outer):
    class Inner (object):
        def outer (self):
            return _Outer

        def doSomething (self):
            outer = self.outer ()
            # Call your static mehthods.
            cls = outer.subclassRef ()
            return cls ()

Another possibility:

class _Outer (object):
    # Define your static methods here, e.g.
    @staticmethod
    def subclassRef ():
        return Outer

class Outer (_Outer):
    class Inner (object):
        def outer (self):
            return _Outer

        def doSomething (self):
            outer = self.outer ()
            # Call your static mehthods.
            cls = outer.subclassRef ()
            return cls ()

回答 8

扩展@tsnorri的有说服力的思想,即外部方法可能是静态方法

class Outer(object):

    @staticmethod
    def some_static_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.some_static_method()    # <-- this will work later

    Inner.some_static_method = some_static_method

现在,所讨论的行应该在实际被调用时起作用。

上面代码的最后一行为Inner类提供了一个静态方法,该方法是Outer静态方法的克隆。


这利用了两个Python功能,即功能是对象作用域是文本

通常,本地范围引用(按文本形式)当前函数的本地名称。

…或本例中的当前Class。因此Innersome_static_method可以直接在该定义内引用“外部”类(和)定义的“局部”对象。

Expanding on @tsnorri’s cogent thinking, that the outer method may be a static method:

class Outer(object):

    @staticmethod
    def some_static_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.some_static_method()    # <-- this will work later

    Inner.some_static_method = some_static_method

Now the line in question should work by the time it is actually called.

The last line in the above code gives the Inner class a static method that’s a clone of the Outer static method.


This takes advantage of two Python features, that functions are objects, and scope is textual.

Usually, the local scope references the local names of the (textually) current function.

…or current class in our case. So objects “local” to the definition of the Outer class (Inner and some_static_method) may be referred to directly within that definition.


回答 9

几年迟到了….但是扩大@mike rodent的精彩的回答,我提供我自己的例子低于正好显示了如何灵活的他的解决办法是,为什么它应该是(或应该已经被)接受回答。

Python 3.7

class Parent():

    def __init__(self, name):
        self.name = name
        self.children = []

    class Inner(object):
        pass

    def Child(self, name):
        parent = self
        class Child(Parent.Inner):
            def __init__(self, name):
                self.name = name
                self.parent = parent
                parent.children.append(self)
        return Child(name)



parent = Parent('Bar')

child1 = parent.Child('Foo')
child2 = parent.Child('World')

print(
    # Getting its first childs name
    child1.name, # From itself
    parent.children[0].name, # From its parent
    # Also works with the second child
    child2.name,
    parent.children[1].name,
    # Go nuts if you want
    child2.parent.children[0].name,
    child1.parent.children[1].name
)

print(
    # Getting the parents name
    parent.name, # From itself
    child1.parent.name, # From its children
    child2.parent.name,
    # Go nuts again if you want
    parent.children[0].parent.name,
    parent.children[1].parent.name,
    # Or insane
    child2.parent.children[0].parent.children[1].parent.name,
    child1.parent.children[1].parent.children[0].parent.name
)


# Second parent? No problem
parent2 = Parent('John')
child3 = parent2.Child('Doe')
child4 = parent2.Child('Appleseed')

print(
    child3.name, parent2.children[0].name,
    child4.name, parent2.children[1].name,
    parent2.name # ....
)

输出:

Foo Foo World World Foo World
Bar Bar Bar Bar Bar Bar Bar
Doe Doe Appleseed Appleseed John

再次,一个很好的答案,迈克的道具!

A few years late to the party…. but to expand on @mike rodent‘s wonderful answer, I’ve provided my own example below that shows just how flexible his solution is, and why it should be (or should have been) the accepted answer.

Python 3.7

class Parent():

    def __init__(self, name):
        self.name = name
        self.children = []

    class Inner(object):
        pass

    def Child(self, name):
        parent = self
        class Child(Parent.Inner):
            def __init__(self, name):
                self.name = name
                self.parent = parent
                parent.children.append(self)
        return Child(name)



parent = Parent('Bar')

child1 = parent.Child('Foo')
child2 = parent.Child('World')

print(
    # Getting its first childs name
    child1.name, # From itself
    parent.children[0].name, # From its parent
    # Also works with the second child
    child2.name,
    parent.children[1].name,
    # Go nuts if you want
    child2.parent.children[0].name,
    child1.parent.children[1].name
)

print(
    # Getting the parents name
    parent.name, # From itself
    child1.parent.name, # From its children
    child2.parent.name,
    # Go nuts again if you want
    parent.children[0].parent.name,
    parent.children[1].parent.name,
    # Or insane
    child2.parent.children[0].parent.children[1].parent.name,
    child1.parent.children[1].parent.children[0].parent.name
)


# Second parent? No problem
parent2 = Parent('John')
child3 = parent2.Child('Doe')
child4 = parent2.Child('Appleseed')

print(
    child3.name, parent2.children[0].name,
    child4.name, parent2.children[1].name,
    parent2.name # ....
)

Output:

Foo Foo World World Foo World
Bar Bar Bar Bar Bar Bar Bar
Doe Doe Appleseed Appleseed John

Again, a wonderful answer, props to you mike!


回答 10

这太简单了:

输入:

class A:
    def __init__(self):
        pass

    def func1(self):
        print('class A func1')

    class B:
        def __init__(self):
            a1 = A()
            a1.func1()

        def func1(self):
            print('class B func1')

b = A.B()
b.func1()

输出量

A类func1

B类func1

It is too simple:

Input:

class A:
    def __init__(self):
        pass

    def func1(self):
        print('class A func1')

    class B:
        def __init__(self):
            a1 = A()
            a1.func1()

        def func1(self):
            print('class B func1')

b = A.B()
b.func1()

Output

class A func1

class B func1


首次使用后重新分配时,局部变量上出现UnboundLocalError

问题:首次使用后重新分配时,局部变量上出现UnboundLocalError

以下代码可在Python 2.5和3.0中正常工作:

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

但是,当我取消注释(B)行时,我UnboundLocalError: 'c' not assigned(A)行得到了注释。的值ab被正确地打印。这使我完全困惑,原因有两个:

  1. 为什么由于行(B)的后面的语句而在行(A)抛出运行时错误?

  2. 为什么在按预期方式打印变量a并产生错误?bc

我能提出的唯一解释是,局部变量c是由赋值创建的c+=1,它c甚至在创建局部变量之前就优先于“全局”变量。当然,在变量存在之前“窃取”范围是没有意义的。

有人可以解释这种现象吗?

The following code works as expected in both Python 2.5 and 3.0:

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

However, when I uncomment line (B), I get an UnboundLocalError: 'c' not assigned at line (A). The values of a and b are printed correctly. This has me completely baffled for two reasons:

  1. Why is there a runtime error thrown at line (A) because of a later statement on line (B)?

  2. Why are variables a and b printed as expected, while c raises an error?

The only explanation I can come up with is that a local variable c is created by the assignment c+=1, which takes precedent over the “global” variable c even before the local variable is created. Of course, it doesn’t make sense for a variable to “steal” scope before it exists.

Could someone please explain this behavior?


回答 0

Python对函数中的变量的处理方式有所不同,具体取决于您是从函数内部还是外部为其分配值。如果在函数中分配了变量,则默认情况下会将其视为局部变量。因此,当取消注释该行时,您将尝试c在分配任何值之前引用该局部变量。

如果要让变量c引用c = 3该函数之前分配的全局变量,请输入

global c

作为函数的第一行。

至于python 3,现在有

nonlocal c

您可以用来引用最近的包含c变量的封闭函数范围。

Python treats variables in functions differently depending on whether you assign values to them from inside or outside the function. If a variable is assigned within a function, it is treated by default as a local variable. Therefore, when you uncomment the line you are trying to reference the local variable c before any value has been assigned to it.

If you want the variable c to refer to the global c = 3 assigned before the function, put

global c

as the first line of the function.

As for python 3, there is now

nonlocal c

that you can use to refer to the nearest enclosing function scope that has a c variable.


回答 1

Python有点怪异之处在于,它把所有内容都保存在字典中以适应各种范围。原始的a,b,c在最高范围内,因此在该最高字典中。该函数具有其自己的字典。当到达print(a)and print(b)语句时,字典中没有该名称的任何内容,因此Python查找列表并在全局字典中找到它们。

现在我们到达c+=1,它当然等于c=c+1。当Python扫描该行时,它说:“啊哈,有一个名为c的变量,我将其放入本地范围字典中。” 然后,当它在赋值的右侧为c寻找c的值时,会找到名为c的局部变量,该变量尚无值,因此引发错误。

global c上面提到的语句只是告诉解析器它使用c全局范围内的,因此不需要一个新的语句。

它之所以说在线上存在问题,是因为它在尝试生成代码之前就在有效地寻找名称,因此从某种意义上说,它还没有真正做到这一点。我认为这是一个可用性错误,但是通常最好的做法是只学习不要过于重视编译器的消息。

如果可以的话,我可能花了一天的时间来研究和试验相同的问题,然后才发现Guido写了一些有关解释一切的字典的东西。

更新,请参阅评论:

它不会扫描代码两次,但是会在两个阶段(词法分析和解析)中扫描代码。

考虑一下这一行代码的解析方式。词法分析器读取源文本并将其分解为词素,即语法的“最小组件”。所以当它到达终点时

c+=1

它分解成类似

SYMBOL(c) OPERATOR(+=) DIGIT(1)

解析器最终希望将其放入解析树中并执行它,但是由于它是一个赋值,因此在解析树之前,它会在本地字典中查找名称c,没有看到它,然后将其插入字典中,它未初始化。用完全编译的语言,它将只进入符号表并等待解析,但是由于它没有第二遍的奢侈,因此词法分析器做了一些额外的工作以使以后的生活更轻松。仅然后,它看到操作员,看到规则说“如果您有操作员+ =,则必须已经初始化了左侧”,并说“哇!”

这里的要点是它还没有真正开始解析该行。这一切都是为实际解析做准备,因此行计数器尚未前进到下一行。因此,当它发出错误信号时,它仍会在前一行上考虑它。

正如我所说,您可能会争辩说这是一个可用性错误,但这实际上是相当普遍的事情。一些编译器对此更为诚实,并说“ XXX行或其附近的错误”,但事实并非如此。

Python is a little weird in that it keeps everything in a dictionary for the various scopes. The original a,b,c are in the uppermost scope and so in that uppermost dictionary. The function has its own dictionary. When you reach the print(a) and print(b) statements, there’s nothing by that name in the dictionary, so Python looks up the list and finds them in the global dictionary.

Now we get to c+=1, which is, of course, equivalent to c=c+1. When Python scans that line, it says “aha, there’s a variable named c, I’ll put it into my local scope dictionary.” Then when it goes looking for a value for c for the c on the right hand side of the assignment, it finds its local variable named c, which has no value yet, and so throws the error.

The statement global c mentioned above simply tells the parser that it uses the c from the global scope and so doesn’t need a new one.

The reason it says there’s an issue on the line it does is because it is effectively looking for the names before it tries to generate code, and so in some sense doesn’t think it’s really doing that line yet. I’d argue that is a usability bug, but it’s generally a good practice to just learn not to take a compiler’s messages too seriously.

If it’s any comfort, I spent probably a day digging and experimenting with this same issue before I found something Guido had written about the dictionaries that Explained Everything.

Update, see comments:

It doesn’t scan the code twice, but it does scan the code in two phases, lexing and parsing.

Consider how the parse of this line of code works. The lexer reads the source text and breaks it into lexemes, the “smallest components” of the grammar. So when it hits the line

c+=1

it breaks it up into something like

SYMBOL(c) OPERATOR(+=) DIGIT(1)

The parser eventually wants to make this into a parse tree and execute it, but since it’s an assignment, before it does, it looks for the name c in the local dictionary, doesn’t see it, and inserts it in the dictionary, marking it as uninitialized. In a fully compiled language, it would just go into the symbol table and wait for the parse, but since it WON’T have the luxury of a second pass, the lexer does a little extra work to make life easier later on. Only, then it sees the OPERATOR, sees that the rules say “if you have an operator += the left hand side must have been initialized” and says “whoops!”

The point here is that it hasn’t really started the parse of the line yet. This is all happening sort of preparatory to the actual parse, so the line counter hasn’t advanced to the next line. Thus when it signals the error, it still thinks its on the previous line.

As I say, you could argue it’s a usability bug, but its actually a fairly common thing. Some compilers are more honest about it and say “error on or around line XXX”, but this one doesn’t.


回答 2

看一下反汇编可以澄清正在发生的事情:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

如您所见,访问a的字节码是LOAD_FAST,访问b 的字节码LOAD_GLOBAL。这是因为编译器已经确定在函数内已将a分配给它,并将其归类为局部变量。局部变量的访问机制与全局变量的根本不同-它们在帧的变量表中静态分配了一个偏移量,这意味着查找是一个快速索引,而不是全局变量更昂贵的dict查找。因此,Python将print a行读为“获取插槽0中保存的局部变量’a’的值,并打印出来”,并且当它检测到该变量仍未初始化时,将引发异常。

Taking a look at the disassembly may clarify what is happening:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

As you can see, the bytecode for accessing a is LOAD_FAST, and for b, LOAD_GLOBAL. This is because the compiler has identified that a is assigned to within the function, and classified it as a local variable. The access mechanism for locals is fundamentally different for globals – they are statically assigned an offset in the frame’s variables table, meaning lookup is a quick index, rather than the more expensive dict lookup as for globals. Because of this, Python is reading the print a line as “get the value of local variable ‘a’ held in slot 0, and print it”, and when it detects that this variable is still uninitialised, raises an exception.


回答 3

当您尝试传统的全局变量语义时,Python具有相当有趣的行为。我不记得详细信息,但是您可以很好地读取在“全局”范围内声明的变量的值,但是如果要修改它,则必须使用global关键字。尝试更改test()为此:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

另外,出现此错误的原因是因为您还可以在该函数内声明一个新变量,其名称与“全局”变量相同,因此它将是完全独立的。解释器认为您正在尝试在此范围内创建一个新变量,c并在一个操作中对其进行全部修改,这在Python中是不允许的,因为c未初始化此新变量。

Python has rather interesting behavior when you try traditional global variable semantics. I don’t remember the details, but you can read the value of a variable declared in ‘global’ scope just fine, but if you want to modify it, you have to use the global keyword. Try changing test() to this:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

Also, the reason you are getting this error is because you can also declare a new variable inside that function with the same name as a ‘global’ one, and it would be completely separate. The interpreter thinks you are trying to make a new variable in this scope called c and modify it all in one operation, which isn’t allowed in Python because this new c wasn’t initialized.


回答 4

清楚说明的最佳示例是:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

在调用时foo(),尽管我们永远都不会到达line ,但这也会引发问题 ,因此从逻辑上讲,绝对不应创建局部变量。UnboundLocalErrorbar=0

神秘之处在于“ Python是一种解释性语言 ”,并且函数的声明foo被解释为单个语句(即复合语句),它只是笨拙地解释它并创建局部和全局作用域。因此bar在执行之前会在本地范围内被识别。

有关此类的更多示例,请阅读以下文章:http : //blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

这篇文章提供了Python变量作用域的完整说明和分析:

The best example that makes it clear is:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

when calling foo() , this also raises UnboundLocalError although we will never reach to line bar=0, so logically local variable should never be created.

The mystery lies in “Python is an Interpreted Language” and the declaration of the function foo is interpreted as a single statement (i.e. a compound statement), it just interprets it dumbly and creates local and global scopes. So bar is recognized in local scope before execution.

For more examples like this Read this post: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

This post provides a Complete Description and Analyses of the Python Scoping of variables:


回答 5

这里有两个链接可能会有所帮助

1:当变量具有值时,docs.python.org / 3.1 / faq / programming.html?highlight = nonlocal#why-am-i-getting-unboundboundlocalerror-

2:docs.python.org/3.1/faq/programming.html?highlight = nonlocal#how-do-i-write-a-function-with-output-parameters-call by reference

链接一描述了错误UnboundLocalError。链接二可以帮助您重写测试功能。根据链接二,原始问题可以重写为:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)

Here are two links that may help

1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference

link one describes the error UnboundLocalError. Link two can help with with re-writing your test function. Based on link two, the original problem could be rewritten as:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)

回答 6

这不是您问题的直接答案,而是紧密相关的,因为这是由扩展分配和函数作用域之间的关系引起的另一个难题。

在大多数情况下,您倾向于认为扩充分配(a += b)与简单分配(a = a + b)完全等效。不过,在一个极端的情况下,可能会遇到一些麻烦。让我解释:

Python的简单分配的工作方式意味着,如果a将其传递到函数中(如func(a);请注意,Python始终是按引用传递),则a = a + b不会修改所a传递的。相反,它将仅修改的本地指针a

但是,如果使用a += b,则有时可以实现为:

a = a + b

或有时(如果该方法存在)为:

a.__iadd__(b)

在第一种情况下(只要a未声明为全局),局部作用域之外就没有副作用,因为对的赋值a只是指针更新。

在第二种情况下,a实际上将修改自身,因此对的所有引用都a将指向修改后的版本。以下代码演示了这一点:

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

因此,诀窍是避免在函数参数上增加分配(我尝试仅将其用于局部/循环变量)。使用简单的分配,您就可以避免歧义行为。

This is not a direct answer to your question, but it is closely related, as it’s another gotcha caused by the relationship between augmented assignment and function scopes.

In most cases, you tend to think of augmented assignment (a += b) as exactly equivalent to simple assignment (a = a + b). It is possible to get into some trouble with this though, in one corner case. Let me explain:

The way Python’s simple assignment works means that if a is passed into a function (like func(a); note that Python is always pass-by-reference), then a = a + b will not modify the a that is passed in. Instead, it will just modify the local pointer to a.

But if you use a += b, then it is sometimes implemented as:

a = a + b

or sometimes (if the method exists) as:

a.__iadd__(b)

In the first case (as long as a is not declared global), there are no side-effects outside local scope, as the assignment to a is just a pointer update.

In the second case, a will actually modify itself, so all references to a will point to the modified version. This is demonstrated by the following code:

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

So the trick is to avoid augmented assignment on function arguments (I try to only use it for local/loop variables). Use simple assignment, and you will be safe from ambiguous behaviour.


回答 7

Python解释器将读取一个函数作为一个完整的单元。我认为它是通过两次读取来读取的,一次是收集其闭包(局部变量),另一次是将其转换为字节码。

如您所知,您可能已经知道,在’=’左边使用的任何名称都隐含一个局部变量。我不止一次地通过将变量访问更改为+ =被发现,这突然是一个不同的变量。

我还想指出,这实际上与全局范围无关。嵌套函数具有相同的行为。

The Python interpreter will read a function as a complete unit. I think of it as reading it in two passes, once to gather its closure (the local variables), then again to turn it into byte-code.

As I’m sure you were already aware, any name used on the left of a ‘=’ is implicitly a local variable. More than once I’ve been caught out by changing a variable access to a += and it’s suddenly a different variable.

I also wanted to point out it’s not really anything to do with global scope specifically. You get the same behaviour with nested functions.


回答 8

c+=1Assigns c,python假定已分配的变量是局部变量,但在这种情况下,尚未在本地声明。

使用globalnonlocal关键字。

nonlocal 仅在python 3中有效,因此,如果您使用的是python 2,并且不想将变量设置为全局变量,则可以使用可变对象:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()

c+=1 assigns c, python assumes assigned variables are local, but in this case it hasn’t been declared locally.

Either use the global or nonlocal keywords.

nonlocal works only in python 3, so if you’re using python 2 and don’t want to make your variable global, you can use a mutable object:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()

回答 9

到达类变量的最佳方法是直接通过类名称访问

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1

The best way to reach class variable is directly accesing by class name

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1

回答 10

在python中,对于所有类型的变量,局部变量,类变量和全局变量,我们都有类似的声明。当您从方法引用全局变量时,python认为您实际上是在从方法本身引用变量,而该变量尚未定义,因此会引发错误。要引用全局变量,我们必须使用globals()[‘variableName’]。

在您的情况下,请分别使用globals()[‘a],globals()[‘b’]和globals()[‘c’]代替a,b和c。

In Python we have similar declaration for all type of variables: local, class, and global variables. When you refer to a global variable from a method, Python thinks that you are actually referring to a variable from the method itself, which is not yet defined and hence it throws an error.

To refer global variable we have to use globals()['variableName'].

in your case use globals()['a], globals()['b'] and globals()['c'] instead of a,b and c respectively.


回答 11

同样的问题困扰着我。使用nonlocalglobal可以解决问题。
但是,使用时需要注意nonlocal,它适用于嵌套函数。但是,在模块级别,它不起作用。在此处查看示例

The same problem bothers me. Using nonlocal and global can solve the problem.
However, attention needed for the usage of nonlocal, it works for nested functions. However, in a module level, it does not work. See examples here.


Python“ for”循环的作用域

问题:Python“ for”循环的作用域

我不是在问Python的作用域规则。我大致了解作用域在Python中用于循环的原理。我的问题是为什么设计决策是以这种方式做出的。例如(无双关语):

for foo in xrange(10):
    bar = 2
print(foo, bar)

上面将打印(9,2)。

这让我感到很奇怪:“ foo”实际上只是在控制循环,而“ bar”是在循环内部定义的。我可以理解为什么可能需要在循环外部访问“ bar”(否则,for循环的功能将非常有限)。我不明白的是为什么循环退出后,控制变量必须保留在范围内。以我的经验,它只会使全局命名空间混乱,并且使查找其他语言的解释器捕获的错误变得更加困难。

I’m not asking about Python’s scoping rules; I understand generally how scoping works in Python for loops. My question is why the design decisions were made in this way. For example (no pun intended):

for foo in xrange(10):
    bar = 2
print(foo, bar)

The above will print (9,2).

This strikes me as weird: ‘foo’ is really just controlling the loop, and ‘bar’ was defined inside the loop. I can understand why it might be necessary for ‘bar’ to be accessible outside the loop (otherwise, for loops would have very limited functionality). What I don’t understand is why it is necessary for the control variable to remain in scope after the loop exits. In my experience, it simply clutters the global namespace and makes it harder to track down errors that would be caught by interpreters in other languages.


回答 0

最可能的答案是,它只是使语法简单,没有成为采用的绊脚石,而且许多人都对在循环结构中分配名称时不必消除名称的范围感到满意。变量不在范围内声明,而是由赋值语句的位置隐含。该global关键字存在只是为了这个原因(象征分配在全球范围内完成)。

更新资料

这里是关于该主题的精彩讨论:http : //mail.python.org/pipermail/python-ideas/2008-October/002109.html

以前的使for循环变量位于循环本地的建议,偶然发现了现有代码的问题,该代码依赖循环变量在退出循环后保持其值,这似乎被认为是理想的功能。

简而言之,您可以将其归咎于Python社区:P

The likeliest answer is that it just keeps the grammar simple, hasn’t been a stumbling block for adoption, and many have been happy with not having to disambiguate the scope to which a name belongs when assigning to it within a loop construct. Variables are not declared within a scope, it is implied by the location of assignment statements. The global keyword exists just for this reason (to signify that assignment is done at a global scope).

Update

Here’s a good discussion on the topic: http://mail.python.org/pipermail/python-ideas/2008-October/002109.html

Previous proposals to make for-loop variables local to the loop have stumbled on the problem of existing code that relies on the loop variable keeping its value after exiting the loop, and it seems that this is regarded as a desirable feature.

In short, you can probably blame it on the Python community :P


回答 1

Python不像其他一些语言(例如C / C ++或Java)一样没有块。因此,Python中的作用域单位是一个函数。

Python does not have blocks, as do some other languages (such as C/C++ or Java). Therefore, scoping unit in Python is a function.


回答 2

一个非常有用的案例是使用时enumerate,您希望最终总数:

for count, x in enumerate(someiterator, start=1):
    dosomething(count, x)
print "I did something {0} times".format(count)

这有必要吗?不会。但是,这确实很方便。

要注意的另一件事:在Python 2中,列表推导中的变量也被泄漏:

>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> x
9

但是,这不适用于Python 3。

A really useful case for this is when using enumerate and you want the total count in the end:

for count, x in enumerate(someiterator, start=1):
    dosomething(count, x)
print "I did something {0} times".format(count)

Is this necessary? No. But, it sure is convenient.

Another thing to be aware of: in Python 2, variables in list comprehensions are leaked as well:

>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> x
9

But, the same does not apply to Python 3.


回答 3

如果您在循环中有一个break语句(并且想在以后使用迭代值,也许可以取回代码,为某些内容编制索引或提供状态),它可以节省一行代码和一次赋值,因此很方便。

If you have a break statement in the loop (and want to use the iteration value later, perhaps to pick back up, index something, or give status), it saves you one line of code and one assignment, so there’s a convenience.


回答 4

Python的主要影响力之一是ABC,这是一种在荷兰开发的语言,用于向初学者教授编程概念。Python的创建者Guido van Rossum在1980年代在ABC工作了几年。我对ABC几乎一无所知,但是由于它是面向初学者的,所以我认为它必须具有有限的作用域,就像早期的BASIC一样。

One of the primary influences for Python is ABC, a language developed in the Netherlands for teaching programming concepts to beginners. Python’s creator, Guido van Rossum, worked on ABC for several years in the 1980s. I know almost nothing about ABC, but as it is intended for beginners, I suppose it must have a limited number of scopes, much like early BASICs.


回答 5

对于初学者来说,如果变量是循环的局部变量,那么这些循环对于大多数现实世界的编程将毫无用处。

在当前情况下:

# Sum the values 0..9
total = 0
for foo in xrange(10):
    total = total + foo
print total

Yield45。现在,考虑分配在Python中的工作方式。如果循环变量严格是局部变量:

# Sum the values 0..9?
total = 0
for foo in xrange(10):
    # Create a new integer object with value "total + foo" and bind it to a new
    # loop-local variable named "total".
    total = total + foo
print total

产生0,因为total赋值后的循环内部与循环total外部的变量不同。这不是最佳或预期的行为。

For starters, if variables were local to loops, those loops would be useless for most real-world programming.

In the current situation:

# Sum the values 0..9
total = 0
for foo in xrange(10):
    total = total + foo
print total

yields 45. Now, consider how assignment works in Python. If loop variables were strictly local:

# Sum the values 0..9?
total = 0
for foo in xrange(10):
    # Create a new integer object with value "total + foo" and bind it to a new
    # loop-local variable named "total".
    total = total + foo
print total

yields 0, because total inside the loop after the assignment is not the same variable as total outside the loop. This would not be optimal or expected behavior.


在if语句中初始化的变量的作用域是什么?

问题:在if语句中初始化的变量的作用域是什么?

我是Python的新手,所以这可能是一个简单的范围界定问题。Python文件(模块)中的以下代码使我有些困惑:

if __name__ == '__main__':
    x = 1

print x

在我使用过的其他语言中,此代码将引发异常,因为该x变量是if语句的局部变量,不应在其外部存在。但是此代码将执行并打印1。任何人都可以解释此行为吗?是否在模块中创建的所有变量都是全局的/可用于整个模块?

I’m new to Python, so this is probably a simple scoping question. The following code in a Python file (module) is confusing me slightly:

if __name__ == '__main__':
    x = 1

print x

In other languages I’ve worked in, this code would throw an exception, as the x variable is local to the if statement and should not exist outside of it. But this code executes, and prints 1. Can anyone explain this behavior? Are all variables created in a module global/available to the entire module?


回答 0

Python变量的作用域是分配给它们的最里面的函数,类或模块。控制块(如ifwhile块)不计在内,因此在内分配的变量的if作用域仍限于函数,类或模块。

(由生成器表达式或list / set / dict理解定义的隐式函数与lambda表达式一样进行计数。您不能将赋值语句填充到其中任何一个中,但是lambda参数和for子句目标是隐式赋值。)

Python variables are scoped to the innermost function, class, or module in which they’re assigned. Control blocks like if and while blocks don’t count, so a variable assigned inside an if is still scoped to a function, class, or module.

(Implicit functions defined by a generator expression or list/set/dict comprehension do count, as do lambda expressions. You can’t stuff an assignment statement into any of those, but lambda parameters and for clause targets are implicit assignment.)


回答 1

是的,它们在同一个“本地范围”中,实际上这样的代码在Python中很常见:

if condition:
  x = 'something'
else:
  x = 'something else'

use(x)

请注意,x不会在条件之前声明或初始化它,例如在C或Java中。

换句话说,Python没有块级作用域。不过,请注意以下示例

if False:
    x = 3
print(x)

这显然会引发NameErrorexceptions。

Yes, they’re in the same “local scope”, and actually code like this is common in Python:

if condition:
  x = 'something'
else:
  x = 'something else'

use(x)

Note that x isn’t declared or initialized before the condition, like it would be in C or Java, for example.

In other words, Python does not have block-level scopes. Be careful, though, with examples such as

if False:
    x = 3
print(x)

which would clearly raise a NameError exception.


回答 2

python中的作用域遵循以下顺序:

  • 搜索本地范围

  • 搜索所有封闭函数的范围

  • 搜索全球范围

  • 搜索内置

来源

请注意,if未列出其他循环/分支构造-仅类,函数和模块在Python中提供了作用域,因此,在if块中声明的任何内容都与在该块之外清除的任何内容具有相同的作用域。在编译时不检查变量,这就是为什么其他语言会引发异常的原因。在python中,只要变量在您需要时存在,就不会抛出异常。

Scope in python follows this order:

  • Search the local scope

  • Search the scope of any enclosing functions

  • Search the global scope

  • Search the built-ins

(source)

Notice that if and other looping/branching constructs are not listed – only classes, functions, and modules provide scope in Python, so anything declared in an if block has the same scope as anything decleared outside the block. Variables aren’t checked at compile time, which is why other languages throw an exception. In python, so long as the variable exists at the time you require it, no exception will be thrown.


回答 3

正如Eli所说,Python不需要变量声明。在C中,您会说:

int x;
if(something)
    x = 1;
else
    x = 2;

但在Python中声明是隐式的,因此当您分配给x时,它会自动声明。这是因为Python是动态类型的-它无法在静态类型的语言中工作,因为取决于所使用的路径,可能会在未声明的情况下使用变量。这将在编译时以静态类型的语言捕获,但是允许使用动态类型的语言。

if由于此问题,静态类型的语言仅限于必须在语句之外声明变量的唯一原因。拥抱动态!

As Eli said, Python doesn’t require variable declaration. In C you would say:

int x;
if(something)
    x = 1;
else
    x = 2;

but in Python declaration is implicit, so when you assign to x it is automatically declared. It’s because Python is dynamically typed – it wouldn’t work in a statically typed language, because depending on the path used, a variable might be used without being declared. This would be caught at compile time in a statically typed language, but with a dynamically typed language it’s allowed.

The only reason that a statically typed language is limited to having to declare variables outside of if statements in because of this problem. Embrace the dynamic!


回答 4

与C之类的语言不同,Python变量在它所出现的整个函数(或类,模块)的范围内,而不仅仅是在最内部的“块”中。就像您int x在函数(或类,模块)的顶部声明的一样,只是在Python中不必声明变量。

请注意,x仅在运行时(即,进入print x语句时)检查变量的存在。如果__name__不相等"__main__",则会出现异常:NameError: name 'x' is not defined

Unlike languages such as C, a Python variable is in scope for the whole of the function (or class, or module) where it appears, not just in the innermost “block”. It is as though you declared int x at the top of the function (or class, or module), except that in Python you don’t have to declare variables.

Note that the existence of the variable x is checked only at runtime — that is, when you get to the print x statement. If __name__ didn’t equal "__main__" then you would get an exception: NameError: name 'x' is not defined.


回答 5

是。for范围也是如此。但是当然不起作用。

在您的示例中:如果if语句中的条件为false,x则不会定义。

Yes. It is also true for for scope. But not functions of course.

In your example: if the condition in the if statement is false, x will not be defined though.


回答 6

您是从命令行执行此代码的,因此if条件为true且x已设置。比较:

>>> if False:
    y = 42


>>> y
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    y
NameError: name 'y' is not defined

you’re executing this code from command line therefore if conditions is true and x is set. Compare:

>>> if False:
    y = 42


>>> y
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    y
NameError: name 'y' is not defined

回答 7

请注意,由于仅在运行时检查Python类型,因此您可以使用如下代码:

if True:
    x = 2
    y = 4
else:
    x = "One"
    y = "Two"
print(x + y)

但是我很难考虑由于类型问题而导致代码无错误运行的其他方式。

And note that since Python types are only checked at runtime you can have code like:

if True:
    x = 2
    y = 4
else:
    x = "One"
    y = "Two"
print(x + y)

But I’m having trouble thinking of other ways in which the code would operate without an error because of type issues.


范围规则的简短描述?

问题:范围规则的简短描述?

Python范围规则到底是什么?

如果我有一些代码:

code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()

在哪里x找到?一些可能的选择包括以下列表:

  1. 在随附的源文件中
  2. 在类命名空间中
  3. 在函数定义中
  4. 在for循环中,索引变量
  5. 在for循环内

当函数spam传递到其他地方时,执行期间还会有上下文。也许lambda函数传递的方式有所不同?

某个地方必须有一个简单的参考或算法。对于中级Python程序员而言,这是一个令人困惑的世界。

What exactly are the Python scoping rules?

If I have some code:

code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()

Where is x found? Some possible choices include the list below:

  1. In the enclosing source file
  2. In the class namespace
  3. In the function definition
  4. In the for loop index variable
  5. Inside the for loop

Also there is the context during execution, when the function spam is passed somewhere else. And maybe lambda functions pass a bit differently?

There must be a simple reference or algorithm somewhere. It’s a confusing world for intermediate Python programmers.


回答 0

实际上,这是学习Python的第3条关于Python范围解析的简明规则埃德 。(这些规则特定于变量名,而不是属性。如果不加句点引用,则适用这些规则。)

LEGB规则

  • L ocal —在函数(deflambda)中以任何方式分配的名称,但未在该函数中声明为全局

  • E nclosing-function —在任何和所有静态封装函数(deflambda)的本地范围内从内部到外部分配的名称

  • ģ叶形(模块) -在模块文件的顶层分配名称,或通过执行global在声明def文件中

  • : – uilt式(Python)的名称在内置模块的名称预先分配的openrangeSyntaxError,等

因此,在

code1
class Foo:
    code2
    def spam():
        code3
        for code4:
            code5
            x()

for循环中没有自己的名字空间。按照LEGB顺序,范围为

  • L:本地的def spam(在code3code4code5
  • E:任何封闭函数(如果整个示例都在另一个示例中def
  • G:x模块中的全局中是否有任何声明code1
  • B:任何内置x的Python。

x永远不会被发现code2(即使您可能会期望,也请参阅Antti的答案此处)。

Actually, a concise rule for Python Scope resolution, from Learning Python, 3rd. Ed.. (These rules are specific to variable names, not attributes. If you reference it without a period, these rules apply.)

LEGB Rule

  • Local — Names assigned in any way within a function (def or lambda), and not declared global in that function

  • Enclosing-function — Names assigned in the local scope of any and all statically enclosing functions (def or lambda), from inner to outer

  • Global (module) — Names assigned at the top-level of a module file, or by executing a global statement in a def within the file

  • Built-in (Python) — Names preassigned in the built-in names module: open, range, SyntaxError, etc

So, in the case of

code1
class Foo:
    code2
    def spam():
        code3
        for code4:
            code5
            x()

The for loop does not have its own namespace. In LEGB order, the scopes would be

  • L: Local in def spam (in code3, code4, and code5)
  • E: Any enclosing functions (if the whole example were in another def)
  • G: Were there any x declared globally in the module (in code1)?
  • B: Any builtin x in Python.

x will never be found in code2 (even in cases where you might expect it would, see Antti’s answer or here).


回答 1

本质上,Python中唯一引入新作用域的就是函数定义。类是一种特殊情况,因为直接在主体中定义的所有内容都放置在类的命名空间中,但是不能从它们包含的方法(或嵌套类)中直接访问它们。

在您的示例中,只有3个范围可以在其中搜索x:

  • 垃圾邮件的范围-包含在code3和code5(以及code4,循环变量)中定义的所有内容

  • 全局范围-包含code1中定义的所有内容以及Foo(及其后的所有更改)

  • 内置命名空间。有点特殊的情况-它包含各种Python内置函数和类型,例如len()和str()。通常,不应由任何用户代码对此进行修改,因此希望它包含标准功能,而不包含其他任何功能。

仅当您在图片中引入嵌套函数(或lambda)时,才会出现更多作用域。但是,它们的行为几乎与您期望的一样。嵌套函数可以访问本地作用域中的所有内容以及封闭函数的作用域中的任何内容。例如。

def foo():
    x=4
    def bar():
        print x  # Accesses x from foo's scope
    bar()  # Prints 4
    x=5
    bar()  # Prints 5

限制条件:

可以访问除局部函数的变量之外的范围中的变量,但是如果没有进一步的语法,则不能将其反弹到新参数。相反,赋值将创建一个新的局部变量,而不是影响父作用域中的变量。例如:

global_var1 = []
global_var2 = 1

def func():
    # This is OK: It's just accessing, not rebinding
    global_var1.append(4) 

    # This won't affect global_var2. Instead it creates a new variable
    global_var2 = 2 

    local1 = 4
    def embedded_func():
        # Again, this doen't affect func's local1 variable.  It creates a 
        # new local variable also called local1 instead.
        local1 = 5
        print local1

    embedded_func() # Prints 5
    print local1    # Prints 4

为了在功能范围内实际修改全局变量的绑定,需要使用global关键字指定变量是全局变量。例如:

global_var = 4
def change_global():
    global global_var
    global_var = global_var + 1

当前,对于封闭函数范围中的变量,没有任何方法可以做到,但是Python 3引入了一个新关键字“ nonlocal”,它的作用与全局变量类似,但对于嵌套函数范围而言。

Essentially, the only thing in Python that introduces a new scope is a function definition. Classes are a bit of a special case in that anything defined directly in the body is placed in the class’s namespace, but they are not directly accessible from within the methods (or nested classes) they contain.

In your example there are only 3 scopes where x will be searched in:

  • spam’s scope – containing everything defined in code3 and code5 (as well as code4, your loop variable)

  • The global scope – containing everything defined in code1, as well as Foo (and whatever changes after it)

  • The builtins namespace. A bit of a special case – this contains the various Python builtin functions and types such as len() and str(). Generally this shouldn’t be modified by any user code, so expect it to contain the standard functions and nothing else.

More scopes only appear when you introduce a nested function (or lambda) into the picture. These will behave pretty much as you’d expect however. The nested function can access everything in the local scope, as well as anything in the enclosing function’s scope. eg.

def foo():
    x=4
    def bar():
        print x  # Accesses x from foo's scope
    bar()  # Prints 4
    x=5
    bar()  # Prints 5

Restrictions:

Variables in scopes other than the local function’s variables can be accessed, but can’t be rebound to new parameters without further syntax. Instead, assignment will create a new local variable instead of affecting the variable in the parent scope. For example:

global_var1 = []
global_var2 = 1

def func():
    # This is OK: It's just accessing, not rebinding
    global_var1.append(4) 

    # This won't affect global_var2. Instead it creates a new variable
    global_var2 = 2 

    local1 = 4
    def embedded_func():
        # Again, this doen't affect func's local1 variable.  It creates a 
        # new local variable also called local1 instead.
        local1 = 5
        print local1

    embedded_func() # Prints 5
    print local1    # Prints 4

In order to actually modify the bindings of global variables from within a function scope, you need to specify that the variable is global with the global keyword. Eg:

global_var = 4
def change_global():
    global global_var
    global_var = global_var + 1

Currently there is no way to do the same for variables in enclosing function scopes, but Python 3 introduces a new keyword, “nonlocal” which will act in a similar way to global, but for nested function scopes.


回答 2

关于Python3时间,还没有详尽的答案,所以我在这里做了一个答案。4.2.2 Python 3文档名称解析详细介绍了此处描述的大部分内容。

如其他答案中所提供的,本地,封闭,全局和内置有4个基本范围,即LEGB。除此以外,还有一个特殊的范围,即类主体,它不包含该类中定义的方法的封闭范围;类主体内的任何赋值都会使变量从此绑定到类主体中。

特别是,除了和之外,没有块语句创建变量作用域。在Python 2中,列表推导不会创建变量作用域,但是在Python 3中,列表推导内的循环变量是在新作用域中创建的。defclass

证明Class机构的特殊性

x = 0
class X(object):
    y = x
    x = x + 1 # x is now a variable
    z = x

    def method(self):
        print(self.x) # -> 1
        print(x)      # -> 0, the global x
        print(y)      # -> NameError: global name 'y' is not defined

inst = X()
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0)

因此,与函数主体不同,您可以在类主体中将变量重新分配给相同的名称,以获得具有相同名称的类变量。对该名称的进一步查找将解析为class变量。


对于Python的许多新手来说,最大的惊喜之一就是for循环不会创建变量作用域。在Python 2中,列表推导也不创建作用域(而generator和dict则创建!)而是泄漏函数或全局范围中的值:

>>> [ i for i in range(5) ]
>>> i
4

理解可以用作在Python 2中的lambda表达式内创建可修改变量的一种狡猾(或者如果您愿意的话)的方法-lambda表达式确实会创建变量作用域,就像该def语句那样,但是在lambda中不允许使用任何语句。赋值是Python中的语句,表示不允许在lambda中进行变量赋值,但列表推导是一个表达式…

此行为已在Python 3中修复-没有理解表达式或生成器会泄漏变量。


全局实际上意味着模块范围;主要的python模块是__main__; 所有导入的模块都可以通过该sys.modules变量访问;获得__main__可以使用的权限sys.modules['__main__'],或import __main__; 在那里访问和分配属性是完全可以接受的;它们将作为变量显示在主模块的全局范围内。


如果在当前作用域中分配了名称(在类作用域中除外),则该名称将被视为属于该作用域,否则将被视为属于任何分配给该变量的封闭作用域(可能未分配)然而,或者根本没有),或者最后是全球范围。如果该变量被认为是局部变量,但尚未设置或已被删除,则读取变量值将导致UnboundLocalError,这是的子类NameError

x = 5
def foobar():
    print(x)  # causes UnboundLocalError!
    x += 1    # because assignment here makes x a local variable within the function

# call the function
foobar()

作用域可以声明它要使用global关键字明确修改全局变量(模块作用域):

x = 5
def foobar():
    global x
    print(x)
    x += 1

foobar() # -> 5
print(x) # -> 6

即使它被封闭在范围内,这也是可能的:

x = 5
y = 13
def make_closure():
    x = 42
    y = 911
    def func():
        global x # sees the global value
        print(x, y)
        x += 1

    return func

func = make_closure()
func()      # -> 5 911
print(x, y) # -> 6 13

在python 2中,没有简单的方法可以在封闭范围内修改值;通常,这是通过具有可变值(例如长度为1的列表)来模拟的

def make_closure():
    value = [0]
    def get_next_value():
        value[0] += 1
        return value[0]

    return get_next_value

get_next = make_closure()
print(get_next()) # -> 1
print(get_next()) # -> 2

但是,在python 3中,nonlocal可以进行救援:

def make_closure():
    value = 0
    def get_next_value():
        nonlocal value
        value += 1
        return value
    return get_next_value

get_next = make_closure() # identical behavior to the previous example.

nonlocal文件

与在全局语句中列出的名称不同,非本地语句中列出的名称必须引用封闭范围内的现有绑定(不能明确确定应在其中创建新绑定的范围)。

即,nonlocal始终是指已绑定名称的最内层外部非全局范围(即,分配给该全局for变量,包括在with子句中或用作函数参数,包括用作目标变量)。


任何不被认为是当前作用域或任何封闭作用域局部变量的变量都是全局变量。在模块全局词典中查找全局名称;如果找不到,则从内建模块中查找全局变量;模块的名称从python 2更改为python 3; 在python 2中曾经是__builtin__,在python 3中现在被称为builtins。如果分配给buildins模块的属性,此后它将以可读的全局变量的形式对任何模块可见,除非该模块用自己的同名全局变量来遮盖它们。


读取内置模块也很有用;假设您希望在文件的某些部分使用python 3样式的打印功能,但文件的其他部分仍使用该print语句。在Python 2.6-2.7中,您可以使用以下命令来掌握Python 3 print函数:

import __builtin__

print3 = __builtin__.__dict__['print']

from __future__ import print_function实际上不会导入print功能,随时随地在Python 2 -而不是它只是禁止解析规则,print在当前模块中的语句,处理print像任何其他变量标识符,从而使print功能的内建进行查找。

There was no thorough answer concerning Python3 time, so I made an answer here. Most of what is described here is detailed in the 4.2.2 Resolution of names of the Python 3 documentation.

As provided in other answers, there are 4 basic scopes, the LEGB, for Local, Enclosing, Global and Builtin. In addition to those, there is a special scope, the class body, which does not comprise an enclosing scope for methods defined within the class; any assignments within the class body make the variable from there on be bound in the class body.

Especially, no block statement, besides def and class, create a variable scope. In Python 2 a list comprehension does not create a variable scope, however in Python 3 the loop variable within list comprehensions is created in a new scope.

To demonstrate the peculiarities of the class body

x = 0
class X(object):
    y = x
    x = x + 1 # x is now a variable
    z = x

    def method(self):
        print(self.x) # -> 1
        print(x)      # -> 0, the global x
        print(y)      # -> NameError: global name 'y' is not defined

inst = X()
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0)

Thus unlike in function body, you can reassign the variable to the same name in class body, to get a class variable with the same name; further lookups on this name resolve to the class variable instead.


One of the greater surprises to many newcomers to Python is that a for loop does not create a variable scope. In Python 2 the list comprehensions do not create a scope either (while generators and dict comprehensions do!) Instead they leak the value in the function or the global scope:

>>> [ i for i in range(5) ]
>>> i
4

The comprehensions can be used as a cunning (or awful if you will) way to make modifiable variables within lambda expressions in Python 2 – a lambda expression does create a variable scope, like the def statement would, but within lambda no statements are allowed. Assignment being a statement in Python means that no variable assignments in lambda are allowed, but a list comprehension is an expression…

This behaviour has been fixed in Python 3 – no comprehension expressions or generators leak variables.


The global really means the module scope; the main python module is the __main__; all imported modules are accessible through the sys.modules variable; to get access to __main__ one can use sys.modules['__main__'], or import __main__; it is perfectly acceptable to access and assign attributes there; they will show up as variables in the global scope of the main module.


If a name is ever assigned to in the current scope (except in the class scope), it will be considered belonging to that scope, otherwise it will be considered to belonging to any enclosing scope that assigns to the variable (it might not be assigned yet, or not at all), or finally the global scope. If the variable is considered local, but it is not set yet, or has been deleted, reading the variable value will result in UnboundLocalError, which is a subclass of NameError.

x = 5
def foobar():
    print(x)  # causes UnboundLocalError!
    x += 1    # because assignment here makes x a local variable within the function

# call the function
foobar()

The scope can declare that it explicitly wants to modify the global (module scope) variable, with the global keyword:

x = 5
def foobar():
    global x
    print(x)
    x += 1

foobar() # -> 5
print(x) # -> 6

This also is possible even if it was shadowed in enclosing scope:

x = 5
y = 13
def make_closure():
    x = 42
    y = 911
    def func():
        global x # sees the global value
        print(x, y)
        x += 1

    return func

func = make_closure()
func()      # -> 5 911
print(x, y) # -> 6 13

In python 2 there is no easy way to modify the value in the enclosing scope; usually this is simulated by having a mutable value, such as a list with length of 1:

def make_closure():
    value = [0]
    def get_next_value():
        value[0] += 1
        return value[0]

    return get_next_value

get_next = make_closure()
print(get_next()) # -> 1
print(get_next()) # -> 2

However in python 3, the nonlocal comes to rescue:

def make_closure():
    value = 0
    def get_next_value():
        nonlocal value
        value += 1
        return value
    return get_next_value

get_next = make_closure() # identical behavior to the previous example.

The nonlocal documentation says that

Names listed in a nonlocal statement, unlike those listed in a global statement, must refer to pre-existing bindings in an enclosing scope (the scope in which a new binding should be created cannot be determined unambiguously).

i.e. nonlocal always refers to the innermost outer non-global scope where the name has been bound (i.e. assigned to, including used as the for target variable, in the with clause, or as a function parameter).


Any variable that is not deemed to be local to the current scope, or any enclosing scope, is a global variable. A global name is looked up in the module global dictionary; if not found, the global is then looked up from the builtins module; the name of the module was changed from python 2 to python 3; in python 2 it was __builtin__ and in python 3 it is now called builtins. If you assign to an attribute of builtins module, it will be visible thereafter to any module as a readable global variable, unless that module shadows them with its own global variable with the same name.


Reading the builtin module can also be useful; suppose that you want the python 3 style print function in some parts of file, but other parts of file still use the print statement. In Python 2.6-2.7 you can get hold of the Python 3 print function with:

import __builtin__

print3 = __builtin__.__dict__['print']

The from __future__ import print_function actually does not import the print function anywhere in Python 2 – instead it just disables the parsing rules for print statement in the current module, handling print like any other variable identifier, and thus allowing the print the function be looked up in the builtins.


回答 3

其他答案中已经概述了Python 2.x的范围规则。我唯一要补充的是,在Python 3.0中,还有一个非本地范围的概念(由’nonlocal’关键字指示)。这使您可以直接访问外部作用域,并可以进行一些巧妙的技巧,包括词法关闭(没有涉及可变对象的难看的技巧)。

编辑:这是PEP,对此有更多信息。

The scoping rules for Python 2.x have been outlined already in other answers. The only thing I would add is that in Python 3.0, there is also the concept of a non-local scope (indicated by the ‘nonlocal’ keyword). This allows you to access outer scopes directly, and opens up the ability to do some neat tricks, including lexical closures (without ugly hacks involving mutable objects).

EDIT: Here’s the PEP with more information on this.


回答 4

范围的更完整示例:

from __future__ import print_function  # for python 2 support

x = 100
print("1. Global x:", x)
class Test(object):
    y = x
    print("2. Enclosed y:", y)
    x = x + 1
    print("3. Enclosed x:", x)

    def method(self):
        print("4. Enclosed self.x", self.x)
        print("5. Global x", x)
        try:
            print(y)
        except NameError as e:
            print("6.", e)

    def method_local_ref(self):
        try:
            print(x)
        except UnboundLocalError as e:
            print("7.", e)
        x = 200 # causing 7 because has same name
        print("8. Local x", x)

inst = Test()
inst.method()
inst.method_local_ref()

输出:

1. Global x: 100
2. Enclosed y: 100
3. Enclosed x: 101
4. Enclosed self.x 101
5. Global x 100
6. global name 'y' is not defined
7. local variable 'x' referenced before assignment
8. Local x 200

A slightly more complete example of scope:

from __future__ import print_function  # for python 2 support

x = 100
print("1. Global x:", x)
class Test(object):
    y = x
    print("2. Enclosed y:", y)
    x = x + 1
    print("3. Enclosed x:", x)

    def method(self):
        print("4. Enclosed self.x", self.x)
        print("5. Global x", x)
        try:
            print(y)
        except NameError as e:
            print("6.", e)

    def method_local_ref(self):
        try:
            print(x)
        except UnboundLocalError as e:
            print("7.", e)
        x = 200 # causing 7 because has same name
        print("8. Local x", x)

inst = Test()
inst.method()
inst.method_local_ref()

output:

1. Global x: 100
2. Enclosed y: 100
3. Enclosed x: 101
4. Enclosed self.x 101
5. Global x 100
6. global name 'y' is not defined
7. local variable 'x' referenced before assignment
8. Local x 200

回答 5

Python通常使用三个可用的命名空间来解析变量。

在执行过程中的任何时候,至少有三个嵌套作用域可以直接访问其命名空间:最先搜索的最内部作用域包含本地名称;任何封闭函数的命名空间,它们从最近的封闭范围开始搜索;接下来搜索的中间范围包含当前模块的全局名称;最外面的作用域(最后搜索)是包含内置名称的命名空间。

有两个函数:globalslocals,它们向您显示这些命名空间中的两个。

命名空间是由包,模块,类,对象构造和函数创建的。没有其他类型的命名空间。

在这种情况下,x必须在本地命名空间或全局命名空间中解析对名为的函数的调用。

在这种情况下,局部变量是方法函数的主体Foo.spam

全球是-很好-全球。

规则是搜索由方法函数(和嵌套函数定义)创建的嵌套局部空间,然后全局搜索。而已。

没有其他范围。该for语句(以及其他复合语句,如iftry)不会创建新的嵌套作用域。仅定义(包,模块,函数,类和对象实例。)

在类定义中,名称是类命名空间的一部分。 code2,例如,必须由类名限定。一般而言Foo.code2。但是,self.code2由于Python对象会将包含的类视为备用类,因此也可以使用。

对象(类的实例)具有实例变量。这些名称位于对象的命名空间中。它们必须由对象限定。(variable.instance。)

在类方法中,您具有局部变量和全局变量。您说self.variable选择实例作为命名空间。您会注意到,这self是每个类成员函数的参数,使其成为本地命名空间的一部分。

请参见Python作用域规则Python作用域变量作用域

Python resolves your variables with — generally — three namespaces available.

At any time during execution, there are at least three nested scopes whose namespaces are directly accessible: the innermost scope, which is searched first, contains the local names; the namespaces of any enclosing functions, which are searched starting with the nearest enclosing scope; the middle scope, searched next, contains the current module’s global names; and the outermost scope (searched last) is the namespace containing built-in names.

There are two functions: globals and locals which show you the contents two of these namespaces.

Namespaces are created by packages, modules, classes, object construction and functions. There aren’t any other flavors of namespaces.

In this case, the call to a function named x has to be resolved in the local name space or the global namespace.

Local in this case, is the body of the method function Foo.spam.

Global is — well — global.

The rule is to search the nested local spaces created by method functions (and nested function definitions), then search global. That’s it.

There are no other scopes. The for statement (and other compound statements like if and try) don’t create new nested scopes. Only definitions (packages, modules, functions, classes and object instances.)

Inside a class definition, the names are part of the class namespace. code2, for instance, must be qualified by the class name. Generally Foo.code2. However, self.code2 will also work because Python objects look at the containing class as a fall-back.

An object (an instance of a class) has instance variables. These names are in the object’s namespace. They must be qualified by the object. (variable.instance.)

From within a class method, you have locals and globals. You say self.variable to pick the instance as the namespace. You’ll note that self is an argument to every class member function, making it part of the local namespace.

See Python Scope Rules, Python Scope, Variable Scope.


回答 6

在哪里找到x?

找不到x,因为您尚未定义x。:-)如果将其放在代码1(全局)或代码3(本地)中,则可以找到它。

code2(类成员)对于相同类的方法内部的代码不可见-您通常可以使用self来访问它们。code4 / code5(循环)的作用域与code3相同,因此,如果您在其中写入x,则将更改code3中定义的x实例,而不创建新的x。

Python是静态作用域的,因此,如果您将“垃圾邮件”传递给另一个函数,则垃圾邮件仍可访问其来源模块(在code1中定义)以及其他任何包含作用域的模块中的全局变量(请参见下文)。code2成员将再次通过self访问。

lambda和def一样。如果在函数内部使用了lambda,则与定义嵌套函数相同。从Python 2.2开始,可以使用嵌套作用域。在这种情况下,您可以在函数嵌套的任何级别绑定x,Python将选择最里面的实例:

x= 0
def fun1():
    x= 1
    def fun2():
        x= 2
        def fun3():
            return x
        return fun3()
    return fun2()
print fun1(), x

2 0

fun3从最近的包含范围(与fun2关联的函数范围)中看到实例x。但是在fun1和全局中定义的其他x实例不受影响。

在nested_scopes之前(在Python 2.1之前的版本中以及在2.1中,除非您专门使用from-future-import要求功能),fun3不可见fun1和fun2的作用域,因此S.Lott的答案成立,您将获得全局x :

0 0

Where is x found?

x is not found as you haven’t defined it. :-) It could be found in code1 (global) or code3 (local) if you put it there.

code2 (class members) aren’t visible to code inside methods of the same class — you would usually access them using self. code4/code5 (loops) live in the same scope as code3, so if you wrote to x in there you would be changing the x instance defined in code3, not making a new x.

Python is statically scoped, so if you pass ‘spam’ to another function spam will still have access to globals in the module it came from (defined in code1), and any other containing scopes (see below). code2 members would again be accessed through self.

lambda is no different to def. If you have a lambda used inside a function, it’s the same as defining a nested function. In Python 2.2 onwards, nested scopes are available. In this case you can bind x at any level of function nesting and Python will pick up the innermost instance:

x= 0
def fun1():
    x= 1
    def fun2():
        x= 2
        def fun3():
            return x
        return fun3()
    return fun2()
print fun1(), x

2 0

fun3 sees the instance x from the nearest containing scope, which is the function scope associated with fun2. But the other x instances, defined in fun1 and globally, are not affected.

Before nested_scopes — in Python pre-2.1, and in 2.1 unless you specifically ask for the feature using a from-future-import — fun1 and fun2’s scopes are not visible to fun3, so S.Lott’s answer holds and you would get the global x:

0 0

回答 7

在Python中,

分配了值的任何变量对于分配在其中出现的块都是局部的。

如果在当前范围内找不到变量,请参考LEGB顺序。

In Python,

any variable that is assigned a value is local to the block in which the assignment appears.

If a variable can’t be found in the current scope, please refer to the LEGB order.


在函数中使用全局变量

问题:在函数中使用全局变量

如何在函数中创建或使用全局变量?

如果在一个函数中创建全局变量,如何在另一个函数中使用该全局变量?我是否需要将全局变量存储在需要对其进行访问的函数的局部变量中?

How can I create or use a global variable in a function?

If I create a global variable in one function, how can I use that global variable in another function? Do I need to store the global variable in a local variable of the function which needs its access?


回答 0

您可以在其他函数中使用全局变量,方法是像global在分配给它的每个函数中一样声明它:

globvar = 0

def set_globvar_to_one():
    global globvar    # Needed to modify global copy of globvar
    globvar = 1

def print_globvar():
    print(globvar)     # No need for global declaration to read value of globvar

set_globvar_to_one()
print_globvar()       # Prints 1

我想这是因为全局变量是如此危险,Python希望通过显式要求使用global关键字来确保您真正知道这就是要使用的内容。

如果要在模块之间共享全局变量,请参见其他答案。

You can use a global variable in other functions by declaring it as global in each function that assigns to it:

globvar = 0

def set_globvar_to_one():
    global globvar    # Needed to modify global copy of globvar
    globvar = 1

def print_globvar():
    print(globvar)     # No need for global declaration to read value of globvar

set_globvar_to_one()
print_globvar()       # Prints 1

I imagine the reason for it is that, since global variables are so dangerous, Python wants to make sure that you really know that’s what you’re playing with by explicitly requiring the global keyword.

See other answers if you want to share a global variable across modules.


回答 1

如果我正确地理解了您的情况,那么您所看到的是Python处理本地(函数)和全局(模块)命名空间的结果。

假设您有一个像这样的模块:

# sample.py
myGlobal = 5

def func1():
    myGlobal = 42

def func2():
    print myGlobal

func1()
func2()

您可能希望它显示为42,但是它显示为5。如前所述,如果在中添加’ global‘声明func1()func2()则将输出42。

def func1():
    global myGlobal
    myGlobal = 42

这里发生的事情是,Python假定在函数内的任何位置分配给的任何名称都是该函数的本地名称,除非另有明确说明。如果仅从名称读取,并且该名称在本地不存在,它将尝试在任何包含的作用域(例如,模块的全局作用域)中查找该名称。

myGlobal因此,当您为name分配42时,Python将创建一个局部变量,该局部变量遮盖同名的全局变量。该局部变量超出范围并在返回时被垃圾回收func1();同时,func2()除了(未修改的)全局名称外,再也看不到其他任何内容。请注意,此命名空间决定是在编译时发生的,而不是在运行时发生的-如果在分配它之前先读取myGlobalinside 的值func1(),则会得到一个UnboundLocalError,因为Python已经确定它必须是局部变量,但它尚未具有任何关联的价值。但是通过使用’ global‘语句,您告诉Python应该在其他地方查找该名称,而不是在本地分配它。

(我相信这种行为主要是通过优化本地命名空间而引起的-如果没有这种行为,Python的VM每次在函数内部分配新名称时都需要至少执行三个名称查找(以确保名称没有t已存在于模块/内置级别),这会大大减慢非常常见的操作的速度。)

If I’m understanding your situation correctly, what you’re seeing is the result of how Python handles local (function) and global (module) namespaces.

Say you’ve got a module like this:

# sample.py
myGlobal = 5

def func1():
    myGlobal = 42

def func2():
    print myGlobal

func1()
func2()

You might expecting this to print 42, but instead it prints 5. As has already been mentioned, if you add a ‘global‘ declaration to func1(), then func2() will print 42.

def func1():
    global myGlobal
    myGlobal = 42

What’s going on here is that Python assumes that any name that is assigned to, anywhere within a function, is local to that function unless explicitly told otherwise. If it is only reading from a name, and the name doesn’t exist locally, it will try to look up the name in any containing scopes (e.g. the module’s global scope).

When you assign 42 to the name myGlobal, therefore, Python creates a local variable that shadows the global variable of the same name. That local goes out of scope and is garbage-collected when func1() returns; meanwhile, func2() can never see anything other than the (unmodified) global name. Note that this namespace decision happens at compile time, not at runtime — if you were to read the value of myGlobal inside func1() before you assign to it, you’d get an UnboundLocalError, because Python has already decided that it must be a local variable but it has not had any value associated with it yet. But by using the ‘global‘ statement, you tell Python that it should look elsewhere for the name instead of assigning to it locally.

(I believe that this behavior originated largely through an optimization of local namespaces — without this behavior, Python’s VM would need to perform at least three name lookups each time a new name is assigned to inside a function (to ensure that the name didn’t already exist at module/builtin level), which would significantly slow down a very common operation.)


回答 2

您可能想探索命名空间的概念。在Python中,该模块全局数据的自然存放位置:

每个模块都有自己的专用符号表,模块中定义的所有功能都将其用作全局符号表。因此,模块的作者可以在模块中使用全局变量,而不必担心与用户的全局变量的意外冲突。另一方面,如果您知道自己在做什么,则可以使用与引用其功能相同的符号来触摸模块的全局变量modname.itemname

此处描述了模块中全局变量的特定用法- 如何在模块之间共享全局变量?,为了完整起见,这里共享内容:

在单个程序内的模块之间共享信息的规范方法是创建一个特殊的配置模块(通常称为configcfg)。只需将配置模块导入应用程序的所有模块中即可;然后该模块就可以作为全局名称使用。因为每个模块只有一个实例,所以对模块对象所做的任何更改都会在所有地方反映出来。例如:

文件:config.py

x = 0   # Default value of the 'x' configuration setting

档案:mod.py

import config
config.x = 1

档案:main.py

import config
import mod
print config.x

You may want to explore the notion of namespaces. In Python, the module is the natural place for global data:

Each module has its own private symbol table, which is used as the global symbol table by all functions defined in the module. Thus, the author of a module can use global variables in the module without worrying about accidental clashes with a user’s global variables. On the other hand, if you know what you are doing you can touch a module’s global variables with the same notation used to refer to its functions, modname.itemname.

A specific use of global-in-a-module is described here – How do I share global variables across modules?, and for completeness the contents are shared here:

The canonical way to share information across modules within a single program is to create a special configuration module (often called config or cfg). Just import the configuration module in all modules of your application; the module then becomes available as a global name. Because there is only one instance of each module, any changes made to the module object get reflected everywhere. For example:

File: config.py

x = 0   # Default value of the 'x' configuration setting

File: mod.py

import config
config.x = 1

File: main.py

import config
import mod
print config.x

回答 3

Python使用一种简单的启发式方法来确定应从本地和全局加载变量的范围。如果变量名称出现在分配的左侧,但未声明为全局变量,则假定它是局部变量。如果它没有出现在作业的左侧,则假定它是全局的。

>>> import dis
>>> def foo():
...     global bar
...     baz = 5
...     print bar
...     print baz
...     print quux
... 
>>> dis.disassemble(foo.func_code)
  3           0 LOAD_CONST               1 (5)
              3 STORE_FAST               0 (baz)

  4           6 LOAD_GLOBAL              0 (bar)
              9 PRINT_ITEM          
             10 PRINT_NEWLINE       

  5          11 LOAD_FAST                0 (baz)
             14 PRINT_ITEM          
             15 PRINT_NEWLINE       

  6          16 LOAD_GLOBAL              1 (quux)
             19 PRINT_ITEM          
             20 PRINT_NEWLINE       
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE        
>>> 

查看baz如何出现在赋值的左侧foo(),它是唯一的LOAD_FAST变量。

Python uses a simple heuristic to decide which scope it should load a variable from, between local and global. If a variable name appears on the left hand side of an assignment, but is not declared global, it is assumed to be local. If it does not appear on the left hand side of an assignment, it is assumed to be global.

>>> import dis
>>> def foo():
...     global bar
...     baz = 5
...     print bar
...     print baz
...     print quux
... 
>>> dis.disassemble(foo.func_code)
  3           0 LOAD_CONST               1 (5)
              3 STORE_FAST               0 (baz)

  4           6 LOAD_GLOBAL              0 (bar)
              9 PRINT_ITEM          
             10 PRINT_NEWLINE       

  5          11 LOAD_FAST                0 (baz)
             14 PRINT_ITEM          
             15 PRINT_NEWLINE       

  6          16 LOAD_GLOBAL              1 (quux)
             19 PRINT_ITEM          
             20 PRINT_NEWLINE       
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE        
>>> 

See how baz, which appears on the left side of an assignment in foo(), is the only LOAD_FAST variable.


回答 4

如果要在函数中引用全局变量,则可以使用global关键字声明哪些变量是全局变量。您不必在所有情况下都使用它(因为这里的人不正确地声称)-如果在本地作用域或定义此函数的函数的作用域中找不到表达式中引用的名称,则在全局范围内查找该名称变量。

但是,如果分配给未在函数中声明为全局变量的新变量,则该变量将隐式声明为局部变量,并且可能使任何现有的具有相同名称的全局变量都模糊不清。

同样,全局变量是有用的,这与某些OOP狂热者相反,特别是对于较小的脚本(OOP过于杀伤)更是如此。

If you want to refer to a global variable in a function, you can use the global keyword to declare which variables are global. You don’t have to use it in all cases (as someone here incorrectly claims) – if the name referenced in an expression cannot be found in local scope or scopes in the functions in which this function is defined, it is looked up among global variables.

However, if you assign to a new variable not declared as global in the function, it is implicitly declared as local, and it can overshadow any existing global variable with the same name.

Also, global variables are useful, contrary to some OOP zealots who claim otherwise – especially for smaller scripts, where OOP is overkill.


回答 5

如果在一个函数中创建全局变量,如何在另一个函数中使用该变量?

我们可以使用以下功能创建一个全局变量:

def create_global_variable():
    global global_variable # must declare it to be a global first
    # modifications are thus reflected on the module's global scope
    global_variable = 'Foo' 

编写函数实际上不会运行其代码。所以我们调用create_global_variable函数:

>>> create_global_variable()

使用全局变量而不进行修改

只要不希望更改它所指向的对象,就可以使用它:

例如,

def use_global_variable():
    return global_variable + '!!!'

现在我们可以使用全局变量:

>>> use_global_variable()
'Foo!!!'

从函数内部修改全局变量

要将全局变量指向另一个对象,需要再次使用global关键字:

def change_global_variable():
    global global_variable
    global_variable = 'Bar'

请注意,编写此函数后,实际上对其进行更改的代码仍未运行:

>>> use_global_variable()
'Foo!!!'

因此,在调用函数之后:

>>> change_global_variable()

我们可以看到全局变量已更改。global_variable现在该名称指向'Bar'

>>> use_global_variable()
'Bar!!!'

请注意,Python中的“全局”不是真正的全局-只是模块级别的全局。因此,它仅适用于在全局模块中编写的函数。函数会记住编写它们的模块,因此当将它们导出到其他模块时,它们仍会在创建它们的模块中查找全局变量。

具有相同名称的局部变量

如果创建具有相同名称的局部变量,它将覆盖全局变量:

def use_local_with_same_name_as_global():
    # bad name for a local variable, though.
    global_variable = 'Baz' 
    return global_variable + '!!!'

>>> use_local_with_same_name_as_global()
'Baz!!!'

但是使用名称错误的局部变量不会更改全局变量:

>>> use_global_variable()
'Bar!!!'

请注意,除非您确切知道自己在做什么并且有充分的理由这样做,否则应避免使用与全局变量同名的局部变量。我还没有遇到这样的原因。

我们在课堂上得到相同的行为

后面有评论问:

如果我想在一个类内的函数内创建一个全局变量,并想在另一个类内的另一个函数内使用该变量,该怎么办?

在这里,我演示了我们在方法中的行为与常规函数中的行为相同:

class Foo:
    def foo(self):
        global global_variable
        global_variable = 'Foo'

class Bar:
    def bar(self):
        return global_variable + '!!!'

Foo().foo()

现在:

>>> Bar().bar()
'Foo!!!'

但是我建议不要使用全局变量,而应使用类属性,以避免使模块命名空间混乱。还要注意,我们self在这里不使用参数-这些可以是类方法(如果从常规cls参数中更改class属性,则很方便)或静态方法(no selfcls)。

If I create a global variable in one function, how can I use that variable in another function?

We can create a global with the following function:

def create_global_variable():
    global global_variable # must declare it to be a global first
    # modifications are thus reflected on the module's global scope
    global_variable = 'Foo' 

Writing a function does not actually run its code. So we call the create_global_variable function:

>>> create_global_variable()

Using globals without modification

You can just use it, so long as you don’t expect to change which object it points to:

For example,

def use_global_variable():
    return global_variable + '!!!'

and now we can use the global variable:

>>> use_global_variable()
'Foo!!!'

Modification of the global variable from inside a function

To point the global variable at a different object, you are required to use the global keyword again:

def change_global_variable():
    global global_variable
    global_variable = 'Bar'

Note that after writing this function, the code actually changing it has still not run:

>>> use_global_variable()
'Foo!!!'

So after calling the function:

>>> change_global_variable()

we can see that the global variable has been changed. The global_variable name now points to 'Bar':

>>> use_global_variable()
'Bar!!!'

Note that “global” in Python is not truly global – it’s only global to the module level. So it is only available to functions written in the modules in which it is global. Functions remember the module in which they are written, so when they are exported into other modules, they still look in the module in which they were created to find global variables.

Local variables with the same name

If you create a local variable with the same name, it will overshadow a global variable:

def use_local_with_same_name_as_global():
    # bad name for a local variable, though.
    global_variable = 'Baz' 
    return global_variable + '!!!'

>>> use_local_with_same_name_as_global()
'Baz!!!'

But using that misnamed local variable does not change the global variable:

>>> use_global_variable()
'Bar!!!'

Note that you should avoid using the local variables with the same names as globals unless you know precisely what you are doing and have a very good reason to do so. I have not yet encountered such a reason.

We get the same behavior in classes

A follow on comment asks:

what to do if I want to create a global variable inside a function inside a class and want to use that variable inside another function inside another class?

Here I demonstrate we get the same behavior in methods as we do in regular functions:

class Foo:
    def foo(self):
        global global_variable
        global_variable = 'Foo'

class Bar:
    def bar(self):
        return global_variable + '!!!'

Foo().foo()

And now:

>>> Bar().bar()
'Foo!!!'

But I would suggest instead of using global variables you use class attributes, to avoid cluttering the module namespace. Also note we don’t use self arguments here – these could be class methods (handy if mutating the class attribute from the usual cls argument) or static methods (no self or cls).


回答 6

除了已经存在的答案之外,还要使其更加混乱:

在Python中,仅在函数内部引用的变量是 隐式全局的。如果在函数体内的任何位置为变量分配了新值,则假定该变量为local。如果在函数内部为变量分配了新值,则该变量是隐式局部变量,您需要将其显式声明为“ global”。

尽管起初有些令人惊讶,但片刻的考虑可以解释这一点。一方面,要求全局分配变量可防止意外副作用。另一方面,如果所有全局引用都需要全局,那么您将一直使用全局。您必须将对内置函数或导入模块的组件的每个引用声明为全局引用。这种混乱将破坏全球宣言对确定副作用的有用性。

资料来源:Python中局部和全局变量的规则是什么?

In addition to already existing answers and to make this more confusing:

In Python, variables that are only referenced inside a function are implicitly global. If a variable is assigned a new value anywhere within the function’s body, it’s assumed to be a local. If a variable is ever assigned a new value inside the function, the variable is implicitly local, and you need to explicitly declare it as ‘global’.

Though a bit surprising at first, a moment’s consideration explains this. On one hand, requiring global for assigned variables provides a bar against unintended side-effects. On the other hand, if global was required for all global references, you’d be using global all the time. You’d have to declare as global every reference to a built-in function or to a component of an imported module. This clutter would defeat the usefulness of the global declaration for identifying side-effects.

Source: What are the rules for local and global variables in Python?.


回答 7

使用并行执行,如果您不了解发生了什么,全局变量可能会导致意外结果。这是在多处理中使用全局变量的示例。我们可以清楚地看到,每个进程都使用其自己的变量副本:

import multiprocessing
import os
import random
import sys
import time

def worker(new_value):
    old_value = get_value()
    set_value(random.randint(1, 99))
    print('pid=[{pid}] '
          'old_value=[{old_value:2}] '
          'new_value=[{new_value:2}] '
          'get_value=[{get_value:2}]'.format(
          pid=str(os.getpid()),
          old_value=old_value,
          new_value=new_value,
          get_value=get_value()))

def get_value():
    global global_variable
    return global_variable

def set_value(new_value):
    global global_variable
    global_variable = new_value

global_variable = -1

print('before set_value(), get_value() = [%s]' % get_value())
set_value(new_value=-2)
print('after  set_value(), get_value() = [%s]' % get_value())

processPool = multiprocessing.Pool(processes=5)
processPool.map(func=worker, iterable=range(15))

输出:

before set_value(), get_value() = [-1]
after  set_value(), get_value() = [-2]
pid=[53970] old_value=[-2] new_value=[ 0] get_value=[23]
pid=[53971] old_value=[-2] new_value=[ 1] get_value=[42]
pid=[53970] old_value=[23] new_value=[ 4] get_value=[50]
pid=[53970] old_value=[50] new_value=[ 6] get_value=[14]
pid=[53971] old_value=[42] new_value=[ 5] get_value=[31]
pid=[53972] old_value=[-2] new_value=[ 2] get_value=[44]
pid=[53973] old_value=[-2] new_value=[ 3] get_value=[94]
pid=[53970] old_value=[14] new_value=[ 7] get_value=[21]
pid=[53971] old_value=[31] new_value=[ 8] get_value=[34]
pid=[53972] old_value=[44] new_value=[ 9] get_value=[59]
pid=[53973] old_value=[94] new_value=[10] get_value=[87]
pid=[53970] old_value=[21] new_value=[11] get_value=[21]
pid=[53971] old_value=[34] new_value=[12] get_value=[82]
pid=[53972] old_value=[59] new_value=[13] get_value=[ 4]
pid=[53973] old_value=[87] new_value=[14] get_value=[70]

With parallel execution, global variables can cause unexpected results if you don’t understand what is happening. Here is an example of using a global variable within multiprocessing. We can clearly see that each process works with its own copy of the variable:

import multiprocessing
import os
import random
import sys
import time

def worker(new_value):
    old_value = get_value()
    set_value(random.randint(1, 99))
    print('pid=[{pid}] '
          'old_value=[{old_value:2}] '
          'new_value=[{new_value:2}] '
          'get_value=[{get_value:2}]'.format(
          pid=str(os.getpid()),
          old_value=old_value,
          new_value=new_value,
          get_value=get_value()))

def get_value():
    global global_variable
    return global_variable

def set_value(new_value):
    global global_variable
    global_variable = new_value

global_variable = -1

print('before set_value(), get_value() = [%s]' % get_value())
set_value(new_value=-2)
print('after  set_value(), get_value() = [%s]' % get_value())

processPool = multiprocessing.Pool(processes=5)
processPool.map(func=worker, iterable=range(15))

Output:

before set_value(), get_value() = [-1]
after  set_value(), get_value() = [-2]
pid=[53970] old_value=[-2] new_value=[ 0] get_value=[23]
pid=[53971] old_value=[-2] new_value=[ 1] get_value=[42]
pid=[53970] old_value=[23] new_value=[ 4] get_value=[50]
pid=[53970] old_value=[50] new_value=[ 6] get_value=[14]
pid=[53971] old_value=[42] new_value=[ 5] get_value=[31]
pid=[53972] old_value=[-2] new_value=[ 2] get_value=[44]
pid=[53973] old_value=[-2] new_value=[ 3] get_value=[94]
pid=[53970] old_value=[14] new_value=[ 7] get_value=[21]
pid=[53971] old_value=[31] new_value=[ 8] get_value=[34]
pid=[53972] old_value=[44] new_value=[ 9] get_value=[59]
pid=[53973] old_value=[94] new_value=[10] get_value=[87]
pid=[53970] old_value=[21] new_value=[11] get_value=[21]
pid=[53971] old_value=[34] new_value=[12] get_value=[82]
pid=[53972] old_value=[59] new_value=[13] get_value=[ 4]
pid=[53973] old_value=[87] new_value=[14] get_value=[70]

回答 8

您所说的是使用这样的方法:

globvar = 5

def f():
    var = globvar
    print(var)

f()  # Prints 5

但是更好的方法是像这样使用全局变量:

globavar = 5
def f():
    global globvar
    print(globvar)
f()   #prints 5

两者给出相同的输出。

What you are saying is to use the method like this:

globvar = 5

def f():
    var = globvar
    print(var)

f()  # Prints 5

But the better way is to use the global variable like this:

globavar = 5
def f():
    global globvar
    print(globvar)
f()   #prints 5

Both give the same output.


回答 9

事实证明,答案总是很简单。

这是一个小示例模块,具有在main定义中显示它的简单方法:

def five(enterAnumber,sumation):
    global helper
    helper  = enterAnumber + sumation

def isTheNumber():
    return helper

这是如何在main定义中显示它:

import TestPy

def main():
    atest  = TestPy
    atest.five(5,8)
    print(atest.isTheNumber())

if __name__ == '__main__':
    main()

这个简单的代码就是这样,它将执行。希望对您有所帮助。

As it turns out the answer is always simple.

Here is a small sample module with a simple way to show it in a main definition:

def five(enterAnumber,sumation):
    global helper
    helper  = enterAnumber + sumation

def isTheNumber():
    return helper

Here is how to show it in a main definition:

import TestPy

def main():
    atest  = TestPy
    atest.five(5,8)
    print(atest.isTheNumber())

if __name__ == '__main__':
    main()

This simple code works just like that, and it will execute. I hope it helps.


回答 10

您需要在要使用的每个函数中引用全局变量。

如下:

var = "test"

def printGlobalText():
    global var #wWe are telling to explicitly use the global version
    var = "global from printGlobalText fun."
    print "var from printGlobalText: " + var

def printLocalText():
    #We are NOT telling to explicitly use the global version, so we are creating a local variable
    var = "local version from printLocalText fun"
    print "var from printLocalText: " + var

printGlobalText()
printLocalText()
"""
Output Result:
var from printGlobalText: global from printGlobalText fun.
var from printLocalText: local version from printLocalText
[Finished in 0.1s]
"""

You need to reference the global variable in every function you want to use.

As follows:

var = "test"

def printGlobalText():
    global var #wWe are telling to explicitly use the global version
    var = "global from printGlobalText fun."
    print "var from printGlobalText: " + var

def printLocalText():
    #We are NOT telling to explicitly use the global version, so we are creating a local variable
    var = "local version from printLocalText fun"
    print "var from printLocalText: " + var

printGlobalText()
printLocalText()
"""
Output Result:
var from printGlobalText: global from printGlobalText fun.
var from printLocalText: local version from printLocalText
[Finished in 0.1s]
"""

回答 11

您实际上并没有将全局变量存储在本地变量中,而只是创建了对原始全局引用所引用的同一对象的本地引用。请记住,Python中的几乎所有内容都是一个引用对象的名称,在常规操作中不会复制任何内容。

如果不必显式指定标识符何时引用预定义的全局变量,则可能必须显式指定何时标识符是新的局部变量(例如,使用类似“ var”命令的东西)在JavaScript中看到)。由于局部变量在任何严重且不平凡的系统中都比全局变量更普遍,因此在大多数情况下,Python的系统更为有意义。

可能有一种尝试进行猜测的语言,如果存在则使用全局变量,如果不存在则创建本地变量。但是,这很容易出错。例如,导入另一个模块可能会无意中通过该名称引入全局变量,从而改变程序的行为。

You’re not actually storing the global in a local variable, just creating a local reference to the same object that your original global reference refers to. Remember that pretty much everything in Python is a name referring to an object, and nothing gets copied in usual operation.

If you didn’t have to explicitly specify when an identifier was to refer to a predefined global, then you’d presumably have to explicitly specify when an identifier is a new local variable instead (for example, with something like the ‘var’ command seen in JavaScript). Since local variables are more common than global variables in any serious and non-trivial system, Python’s system makes more sense in most cases.

You could have a language which attempted to guess, using a global variable if it existed or creating a local variable if it didn’t. However, that would be very error-prone. For example, importing another module could inadvertently introduce a global variable by that name, changing the behaviour of your program.


回答 12

尝试这个:

def x1():
    global x
    x = 6

def x2():
    global x
    x = x+1
    print x

x = 5
x1()
x2()  # output --> 7

Try this:

def x1():
    global x
    x = 6

def x2():
    global x
    x = x+1
    print x

x = 5
x1()
x2()  # output --> 7

回答 13

如果您有一个具有相同名称的局部变量,则可能要使用globals()函数

globals()['your_global_var'] = 42

In case you have a local variable with the same name, you might want to use the globals() function.

globals()['your_global_var'] = 42

回答 14

接下来,作为附加,使用文件包含所有在本地声明的所有全局变量,然后import as

文件initval.py

Stocksin = 300
Prices = []

文件getstocks.py

import initval as iv

def getmystocks(): 
    iv.Stocksin = getstockcount()


def getmycharts():
    for ic in range(iv.Stocksin):

Following on and as an add on, use a file to contain all global variables all declared locally and then import as:

File initval.py:

Stocksin = 300
Prices = []

File getstocks.py:

import initval as iv

def getmystocks(): 
    iv.Stocksin = getstockcount()


def getmycharts():
    for ic in range(iv.Stocksin):

回答 15

写入全局数组的显式元素显然不需要全局声明,尽管对其进行“批发”确实具有该要求:

import numpy as np

hostValue = 3.14159
hostArray = np.array([2., 3.])
hostMatrix = np.array([[1.0, 0.0],[ 0.0, 1.0]])

def func1():
    global hostValue    # mandatory, else local.
    hostValue = 2.0

def func2():
    global hostValue    # mandatory, else UnboundLocalError.
    hostValue += 1.0

def func3():
    global hostArray    # mandatory, else local.
    hostArray = np.array([14., 15.])

def func4():            # no need for globals
    hostArray[0] = 123.4

def func5():            # no need for globals
    hostArray[1] += 1.0

def func6():            # no need for globals
    hostMatrix[1][1] = 12.

def func7():            # no need for globals
    hostMatrix[0][0] += 0.33

func1()
print "After func1(), hostValue = ", hostValue
func2()
print "After func2(), hostValue = ", hostValue
func3()
print "After func3(), hostArray = ", hostArray
func4()
print "After func4(), hostArray = ", hostArray
func5()
print "After func5(), hostArray = ", hostArray
func6()
print "After func6(), hostMatrix = \n", hostMatrix
func7()
print "After func7(), hostMatrix = \n", hostMatrix

Writing to explicit elements of a global array does not apparently need the global declaration, though writing to it “wholesale” does have that requirement:

import numpy as np

hostValue = 3.14159
hostArray = np.array([2., 3.])
hostMatrix = np.array([[1.0, 0.0],[ 0.0, 1.0]])

def func1():
    global hostValue    # mandatory, else local.
    hostValue = 2.0

def func2():
    global hostValue    # mandatory, else UnboundLocalError.
    hostValue += 1.0

def func3():
    global hostArray    # mandatory, else local.
    hostArray = np.array([14., 15.])

def func4():            # no need for globals
    hostArray[0] = 123.4

def func5():            # no need for globals
    hostArray[1] += 1.0

def func6():            # no need for globals
    hostMatrix[1][1] = 12.

def func7():            # no need for globals
    hostMatrix[0][0] += 0.33

func1()
print "After func1(), hostValue = ", hostValue
func2()
print "After func2(), hostValue = ", hostValue
func3()
print "After func3(), hostArray = ", hostArray
func4()
print "After func4(), hostArray = ", hostArray
func5()
print "After func5(), hostArray = ", hostArray
func6()
print "After func6(), hostMatrix = \n", hostMatrix
func7()
print "After func7(), hostMatrix = \n", hostMatrix

回答 16

我添加此内容是因为我在其他任何答案中都没有看到它,这对于那些在类似问题上苦苦挣扎的人可能很有用。该globals()函数返回一个可变的全局符号字典,您可以在其中“神奇地”使数据可用于其余代码。例如:

from pickle import load
def loaditem(name):
    with open(r"C:\pickle\file\location"+"\{}.dat".format(name), "rb") as openfile:
        globals()[name] = load(openfile)
    return True

from pickle import dump
def dumpfile(name):
    with open(name+".dat", "wb") as outfile:
        dump(globals()[name], outfile)
    return True

只会让您将变量转储/加载到全局命名空间中。超级方便,没有混乱,没有大惊小怪。可以肯定,它仅适用于Python 3。

I’m adding this as I haven’t seen it in any of the other answers and it might be useful for someone struggling with something similar. The globals() function returns a mutable global symbol dictionary where you can “magically” make data available for the rest of your code. For example:

from pickle import load
def loaditem(name):
    with open(r"C:\pickle\file\location"+"\{}.dat".format(name), "rb") as openfile:
        globals()[name] = load(openfile)
    return True

and

from pickle import dump
def dumpfile(name):
    with open(name+".dat", "wb") as outfile:
        dump(globals()[name], outfile)
    return True

Will just let you dump/load variables out of and into the global namespace. Super convenient, no muss, no fuss. Pretty sure it’s Python 3 only.


回答 17

引用要在其中显示更改的类命名空间。

在此示例中,Runner正在使用文件配置中的max。我希望我的测试在跑步者使用它时更改max的值。

main / config.py

max = 15000

main / runner.py

from main import config
def check_threads():
    return max < thread_count 

测试/runner_test.py

from main import runner                # <----- 1. add file
from main.runner import check_threads
class RunnerTest(unittest):
   def test_threads(self):
       runner.max = 0                  # <----- 2. set global 
       check_threads()

Reference the class namespace where you want the change to show up.

In this example, runner is using max from the file config. I want my test to change the value of max when runner is using it.

main/config.py

max = 15000

main/runner.py

from main import config
def check_threads():
    return max < thread_count 

tests/runner_test.py

from main import runner                # <----- 1. add file
from main.runner import check_threads
class RunnerTest(unittest):
   def test_threads(self):
       runner.max = 0                  # <----- 2. set global 
       check_threads()

回答 18

全局变量很好-多重处理除外

与不同平台/环境的多处理相关的全局问题(一方面是Windows / Mac OS,另一方面是Linux)很麻烦。

我将通过一个简单的示例向您展示这个问题,该问题指出了我前一段时间遇到的问题。

如果您想了解Windows / MacO和Linux上为何有所不同的原因,那么您需要知道这是在…上启动新进程的默认机制。

  • Windows / MacO是“生成”的
  • Linux是“ fork”

它们在内存分配和初始化方面有所不同…(但在此不做介绍)。

让我们看一下问题/示例…

import multiprocessing

counter = 0

def do(task_id):
    global counter
    counter +=1
    print(f'task {task_id}: counter = {counter}')

if __name__ == '__main__':

    pool = multiprocessing.Pool(processes=4)
    task_ids = list(range(4))
    pool.map(do, task_ids)

视窗

如果您在Windows上运行此程序(我也想在MacOS上运行),则会得到以下输出…

task 0: counter = 1
task 1: counter = 2
task 2: counter = 3
task 3: counter = 4

的Linux

如果在Linux上运行它,则会得到以下内容。

task 0: counter = 1
task 1: counter = 1
task 2: counter = 1
task 3: counter = 1

Globals are fine – Except with Multiprocessing

Globals in connection with multiprocessing on different platforms/envrionments as Windows/Mac OS on the one side and Linux on the other are troublesome.

I will show you this with a simple example pointing out a problem which I run into some time ago.

If you want to understand, why things are different on Windows/MacOs and Linux you need to know that, the default mechanism to start a new process on …

  • Windows/MacOs is ‘spawn’
  • Linux is ‘fork’

They are different in Memory allocation an initialisation … (but I don’t go into this here).

Let’s have a look at the problem/example …

import multiprocessing

counter = 0

def do(task_id):
    global counter
    counter +=1
    print(f'task {task_id}: counter = {counter}')

if __name__ == '__main__':

    pool = multiprocessing.Pool(processes=4)
    task_ids = list(range(4))
    pool.map(do, task_ids)

Windows

If you run this on Windows (And I suppose on MacOS too), you get the following output …

task 0: counter = 1
task 1: counter = 2
task 2: counter = 3
task 3: counter = 4

Linux

If you run this on Linux, you get the following instead.

task 0: counter = 1
task 1: counter = 1
task 2: counter = 1
task 3: counter = 1