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所以它只要求“绕过”这个限制的方法,而这个问题要求它的动机/技术细节。
答案 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)
此处,bar
是foo
中的静态广告位(“类变量”)。它由构造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中的一个错误。