【问题标题】:Order of evaluation of classes?类的评估顺序?
【发布时间】:2014-06-16 19:47:06
【问题描述】:

在文件lib.py中我定义了一个函数类C和一个枚举类E,如下:

class C:
    a = None
    def meth(self, v):
        if v == E.v1:
            print("In C.meth().v1")
            a = E.v1
        if v == E.v2:
            print("In C.meth().v2")
            a = E.v2


from enum import Enum
class E(Enum):
    print("In Enum")
    v1 = 1
    v2 = 2

然后,我将这两个类导入我的模块main.py 并使用枚举:

from lib import C
from lib import E

c = C()
c.meth(E.v1)

运行时,我得到以下输出:

In Enum
In C.meth().v1

现在,由于 Python 是一种解释型语言(至少在使用 IDLE 时),我预计在方法 meth 中引用枚举时会出错。由于没有错误,而且似乎运行正常,我想知道在同一模块中以及不同模块之间引用类的(排序)规则是什么?为什么没有错误?

【问题讨论】:

    标签: python class python-3.x enums


    【解决方案1】:

    名称查找发生在运行时。因此,当您定义类C 及其方法meth 时,还没有完成对E 的查找。因此,事后定义它不是问题。而是在您调用该方法时进行查找。

    此外,名称查找是通过扩大范围进行的,因此meth 将找到在模块级别声明的原始E,无论您是否将其导入main.py。由于您还在 main.py 中导入了 E,这是对 same 对象的引用,因此您也可以在其中引用相同的枚举值。

    另见此示例:

    >>> def test(): # foo is not defined at this time
            print(foo)
    >>> test()
    NameError: global name 'foo' is not defined
    >>> foo = 'bar' # after defining foo, it works:
    >>> test()
    bar
    

    定义方法时,变量永远不会“嵌入”;这些方法仅包含名称,并且在运行时查找这些名称。然而,由于 Python 进行查找的方式,局部变量的名称总是在“周围”,即使它们还没有被初始化。这可能会导致UnboundLocalErrors:

    >>> def test():
            print(foo)
            foo = 'baz'
    >>> test()
    UnboundLocalError: local variable 'foo' referenced before assignment
    

    人们可能期望foo 会在第一次打印时在外部范围内查找,但因为有一个本地foo(即使它尚未初始化),foo 将始终*解析到本地foo。

    (* nonlocal 语句允许使 foo 非本地,将其解析到外部范围 - 再次所有在该方法中使用 foo。)

    【讨论】:

    • 谢谢。当然,在main.py 级别上导入E 是为了该模块中的引用(调用c.meth 时)。
    • 您的代码添加很有趣。那么,这意味着 Python 在解析定义时保留对未知对象的引用,而在评估表达式时仅将其与实际对象链接?这意味着我可以在 test() 的评估之间更改 foo 的定义?
    • 它不保留对未知对象的引用,而只是保留 name(在这种情况下为foo)。然后只有当函数被执行时,name lookup 恰好找出属于该名称的对象(如果有的话)。是的,您可以在调用之间更改foo 的值以获得不同的结果。
    • 我实际上是指foo 的定义(如它的类型),而不是值。
    • @ysap:名称没有类型。名称所指的对象具有类型。在某些情况下,可以更改对象的类型,但这与 a = 1 和之后的 a = list() 无关。
    【解决方案2】:

    导入模块时,命令从上到下执行。在class-definition 内部,命令也被执行,以定义类内部的方法。一个def定义了一个方法,但是def里面的命令并没有被执行,只是被解析了。

    【讨论】:

    • 好的,那么解释器如何知道E.v1 是什么以便正确解析命令?
    • 它在运行时就知道了。只执行类定义中的命令。这些方法没有执行任何操作。因此,在您的示例中,如果您再次运行“c.meth(E.v1)”,您将再次获得“In C.meth().v1”,而没有“In Enum”。 “In Enum”在类“创建”时打印(实际上就在调用元类以实际创建类之前)。我建议你在调试器(例如 pydevd)中运行它,看看到底发生了什么以及什么时候发生。
    • 它被解析为名称查找,但在方法运行之前它不会进行名称查找。
    【解决方案3】:

    了解代码中评估顺序的最简单方法是观察它的执行情况:

    http://dbgr.cc/q

    按下调试按钮最右侧的播放按钮,它将自动单步执行。

    我认为让您感到困惑的是,当定义 class E 时,E 类中的所有语句都会运行。这是每个类定义的情况。这包括调用 print 函数来表示"In Enum",以及定义E 类的v1 和v2 成员。

    c.meth(E.v1) 行直到C 和E 类都被定义后才会执行,这意味着E.v1 也已经被定义。这就是为什么没有像您期望的那样出现错误的原因。

    【讨论】:

    • 谢谢。从我在代码中插入的print 语句的输出中可以看出执行顺序非常明显(在我的实际代码中还定义了一个__init__() 方法,因此也可以看到它的print)。让我感到困惑的是,我期望一种解释性语言要求在使用它之前定义一个名称。我认为没有所谓的“后期绑定”。显然,我的推理差距是在解释语言中实现 OO 概念。我只是习惯了 C 的思维方式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-05
    • 2022-10-15
    相关资源
    最近更新 更多