为什么Python不允许引用其定义中的类?

时间:2016-01-01 17:27:37

标签: python class language-design

Python(3和2)不允许您在其体内引用类(方法除外):

class A:
    static_attribute = A()

这会在第二行引发NameError,因为'A' is not defined,而这

class A:
    def method(self):
        return A('argument')

工作正常。 在其他语言中,例如Java,前者没有问题,在许多情况下都很有用,比如实现单例。

为什么Python中不可能这样做?这个决定的原因是什么?

修改 的 我编辑了my other question所以它只要求“绕过”这个限制的方法,而这个问题要求它的动机/技术细节。

5 个答案:

答案 0 :(得分:4)

Python是一种动态类型语言,在导入模块时执行语句 。没有类对象的编译定义,该对象是通过执行class语句创建的。

Python本质上像函数一样执行类主体,将生成的本地命名空间形成为主体。因此,以下代码:

class Foo(object):
    bar = baz

大致翻译为:

def _Foo_body():
    bar = baz
    return locals()
Foo = type('Foo', (object,), _Foo_body())

因此,在class语句完成执行之前,不会分配类的名称。在该语句完成之前,您不能在类语句中使用该名称,就像在def语句完成定义之前您不能使用函数一样。

这意味着你可以动态地动态创建类:

def class_with_base(base_class):
    class Foo(base_class):
        pass
    return Foo

您可以将这些类存储在列表中:

classes = [class_with_base(base) for base in list_of_bases]

现在你有一个类的列表,没有全局名称在任何地方引用它们。没有全球名称,我也不能依赖于方法中存在的这样一个名称; return Foo无法工作,因为没有Foo全球可供参考。

接下来,Python支持一个名为元类的概念,它产生的类就像一个类生成实例一样。上面的type()函数是默认元类,但您可以自由地为类提供。元类可以自由地生成任何它真正喜欢的东西,甚至是位类的东西!因此,Python不能预先知道class语句将产生什么类型的对象,并且不能对它最终绑定所用名称的内容做出假设。见What is a metaclass in Python?

所有这些都不是你可以用Java等静态类型语言做的事情。

答案 1 :(得分:1)

类语句的执行与任何其他语句一样。你的第一个例子(大致)等同于

a = A()
A = type('A', (), {'static_attribute': a})

第一行显然会引发NameError,因为A尚未绑定任何内容。

在第二个示例中,在A实际上被称为之前,method未被引用,此时A确实引用了该类。

答案 2 :(得分:0)

本质上,在整个编译完整定义之前,类不存在。这类似于用其他语言显式编写的结束块,Python使用由缩进确定的隐式结束块。

答案 3 :(得分:0)

其他答案很好地解释了为什么你不能在类中引用类的名称,但是你可以使用类方法来访问类。

@classmethod装饰器表示将传递类类型的方法,而不是通常的类实例(self)。这类似于Java的静态方法(还有一个@staticmethod装饰器,它有点不同)。

对于单例,您可以访问类实例来存储对象实例(在类级别定义的属性是在Java类中定义为static的字段):

class A(object):
    instance = None

    @classmethod
    def get_singleton(cls):
        if cls.instance is None:
            print "Creating new instance"
            cls.instance = cls()

        return cls.instance


>>> a1 = A.get_singleton()
Creating new instance

>>> a2 = A.get_singleton()

>>> print a1 is a2
True

您还可以使用类方法来创建java风格的“静态”方法:

class Name(object):

    def __init__(self, name):
        self.name = name

    @classmethod
    def make_as_victoria(cls):
        return cls("Victoria")

    @classmethod
    def make_as_stephen(cls):
        return cls("Stephen")


>>> victoria = Name.make_as_victoria()
>>> stephen = Name.make_as_stephen()

>>> print victoria.name
Victoria
>>> print stephen.name 
Stephen

答案 4 :(得分:-1)

答案是“只是因为”。

它与Python的类型系统无关,或者它是动态的。它与初始化新引入类型的顺序有关。

几个月前,我开发了TXR语言的对象系统,其工作原理如下:

1> (defstruct foo nil (:static bar (new foo)))
#
2> (new foo)
#S(foo)
3> *2.bar
#S(foo)

此处,barfoo中的静态广告位(“类变量”)。它由构造foo

的表达式初始化

为什么可以从function-based API了解新类型的实例化,其中静态类初始化由传入的函数执行。defstruct宏编译调用make-struct-type表达式(new foo)表达式在 static-initfun 参数传递的匿名函数体中结束。在已在foo符号下注册类型后调用此函数。

我们可以轻松修补C implementation of make_struct_type以便打破这个问题。该函数的最后几行是:

    sethash(struct_type_hash, name, stype);

    if (super) {
      mpush(stype, mkloc(su->dvtypes, super));
      memcpy(st->stslot, su->stslot, sizeof (val) * su->nstslots);
    }

    call_stinitfun_chain(st, stype);

    return stype;
  }

call_stinifun_chain执行初始化,最终评估(new foo)并将其存储在bar静态槽中,sethash调用是在其名称下注册类型的

如果我们简单地颠倒调用这些函数的顺序,语言和类型系统仍将是相同的,几乎所有东西都将像以前一样工作。但是,(:static bar (new foo))插槽说明符将失败。

我按顺序调用了调用,因为我希望类型的语言控制方面尽可能完整,然后再将其暴露给用户可定义的初始化。

我无法想到在初始化结构类型时不知道foo的任何原因,更不用说有充分理由了。静态构造创建实例是合法的。例如,我们可以用它来创建“单例”。

这看起来像是Python中的一个错误。