Python类定义内实例化另一个类

6

我正在尝试给一个保存类的实例的类添加一个变量。以下是我的代码的缩短版本。

class Classy :
    def __init__(self) :
        self.hi = "HI!"
    # "CLASSIES" variable holds instances of class "Classy"
    CLASSIES = []
    for i in xrange(0,4) :
        CLASSIES.append(Classy())

在运行代码时,我遇到了以下错误。
Traceback (most recent call last):
  File "classy.py", line 6, in Classy
    CLASSIES.append(Classy())
NameError: name 'Classy' is not defined

在一个类中,有没有另一种方法将该类的实例添加到类/静态变量中?


真正的问题是你想通过这个动作实现什么目标。 - joojaa
1
@joojaa,我不会说这个用例那么晦涩。 - Gareth Latty
好吧,我有点不理解,如果您实际上初始化了子项,那么将它们放入类变量中会使其变得模糊不清。您基本上正在初始化一个将成为具有4个不同实例的固定其他自身的东西。但是可能有任意数量的不同goto人。奇怪的是,优雅的实例不是列表的一部分,如果初始化的类将是列表的一部分,我会理解这一点。或者您正在查看Borgs数组? - joojaa
4个回答

2
最简单的方法是在课程创建后进行,当类已经被定义并且可以使用时:
class Classy :
    CLASSIES = []

    def __init__(self) :
        self.hi = "HI!"

Classy.CLASSIES = [Classy() for _ in xrange(0,4)]

(这里使用列表推导式是为了方便,因为它是构建列表最可读和高效的方法)。
此外,请注意,如果这是一个常量,您应该将其制作成元组而不是列表;如果它不是常量,则不应使用 ALL_CAPS 名称,因为按照惯例,它意味着常量。

是的,列表推导式会构建一个列表,这将会创建一个新的列表,与类定义执行的结果不同,尽管保持相同的列表可能很重要。请查看我在答案中的编辑。 - eyquem
@eyquem 这是最初的创建,所以并不重要。为了模拟它,只需在类中删除最初的创建 - 我只觉得值得放在那里,因为它极不可能有影响,并且它给出了列表将存在的提示(包括编辑器)。如果您出于某种原因需要它存在并且标识保持不变,只需使用+=而不是=即可。 - Gareth Latty
如果这并不重要,因为这是初始创建,那么在类的定义中不需要使用“CLASSIES = []”这一行。这就是BrenBarn代码中的情况。 - eyquem
@eyquem 是的,这并不是必需的,但这是个好主意。它让代码的读者知道类属性的存在,并且让任何具备自动完成功能的编辑器轻松地知道它的存在。即使在功能上没有必要,这也是值得的。这使得代码更易读。 - Gareth Latty
顺便问一句,你被 Delnan 自吹自擂说 CLASSIES 是全球级别的说服了吗?我是不是理解错了什么?在我看来,这是毫无意义和不可持续的。他从何得出这个奇怪的想法? - eyquem

2

类体在类被创建之前执行。因此,在类存在之前,您正在尝试实例化该类。您仍然可以将实例附加到类上,但必须在类体完成后创建它们,例如:

class Classy(object):
    def __init__(self):
        self.hi = "HI!"
    CLASSIES = []

for i in xrange(4):
    Classy.CLASSIES.append(Classy())

然而,我建议您先仔细考虑自己是否真正需要这个有效全局列表,以及是否需要它成为类对象的一部分。就个人而言,我几乎从不这样做。


是的,但是这种方式很脆弱。更好的方法是将导师放在类实例的列表中,并且只有在需要共享它们的情况下才将其初始化为共享列表。这样,如果您需要扩展代码以处理另一个部门,它将很容易地工作。 - joojaa
@Lattyware 好的,我明白了。在许多情况下,使用(实际上)全局数据结构会使这种设计失去资格,这可能是为什么我从未想过这样的事情的原因。当然,在其他领域,例如基于数据库的 Web 应用程序中,有更好的解决方案(例如利用 ORM)。 - user395760
我绝对不是说这是一个常见的需求,但在某些情况下,这可能是有意义的。 - Gareth Latty
@Lattyware 关于全局变量:是的,你应该像对待其他全局变量一样对待类成员,因为它们面临着完全相同的问题。我并不是说我们不应该有任何全局变量(包括类成员)——显然有许多有效的用例(常量;函数/方法;缓存;某些数据本质上是全局的;我相信还有许多其他例子)。然而,我们必须意识到它们只是不同外观的同一件事情。关于不常见的情况:这就是为什么我写了“认真考虑是否需要”,而不是“你不应该这样做”;-) - user395760
明白了,我想其实和我尝试做的没有什么区别!顺便提一下,我避免提及我的实际用例,以避免这个问题演变成“不要那样做,那是错误的”;但是这个类实际上是一个“机器”类,每个机器代表局域网上的一台机器;我通过解析dhcpd.conf获取机器列表,并将其填充到静态类变量中。 - Tanaki
显示剩余13条评论

2
我觉得你想要获得那个东西:
class Classy :
    CLASSIES = []
    def __init__(self) :
        self.hi = "HI!"
        Classy.CLASSIES.append(self)

for i in xrange(4):
    Classy()

for x in  Classy.CLASSIES:
    print x

结果
<__main__.Classy instance at 0x011DF3F0>
<__main__.Classy instance at 0x011DF440>
<__main__.Classy instance at 0x011DF418>
<__main__.Classy instance at 0x011DF2B0>

编辑

请注意,使用Lattyware的代码:

class Classy :
    CLASSIES = []
    idC = id(CLASSIES)
    def __init__(self) :
        self.hi = "HI!"
        #Classy.CLASSIES.append(self)


Classy.CLASSIES = [Classy() for _ in xrange(0,4)]

print Classy.idC
print id(Classy.CLASSIES)
print 'Classy.idC==id(Classy.CLASSIES) :',Classy.idC==id(Classy.CLASSIES)

结果
18713576
10755928
Classy.idC==id(Classy.CLASSIES) : False

使用delnan'code的for循环时,它不会出现。
然而,很容易纠正:

Classy.CLASSIES[:] = [Classy() for _ in xrange(0,4)]
或者
Classy.CLASSIES.extend(Classy() for _ in xrange(0,4))
而不是
Classy.CLASSIES = [Classy() for _ in xrange(0,4)]
这取决于所需的内容。

编辑2

方法可以像普通函数一样引用全局名称。与一个方法关联的全局作用域是包含其定义的模块。(类从不被用作全局作用域。)一个类有一个由字典对象实现的命名空间。类属性引用被翻译成在这个字典中查找,例如,C.x 被翻译成 C.__dict__["x"]
参考链接:
- http://docs.python.org/2/tutorial/classes.html#class-definition-syntax - http://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes
class Classy :
    CLASSIES = []

print '"CLASSIES" in globals()',"CLASSIES" in globals()
print '"CLASSIES" in Classy.__dict__ ==',"CLASSIES" in Classy.__dict__

结果
"CLASSIES" in globals() False
"CLASSIES" in Classy.__dict__ == True

Delnan,您将如何继续假装CLASSIES是全球性的?
在与Lattyware辩论中,我有什么误解吗?

这意味着原帖作者想要将每个类都添加到列表中。 - Gareth Latty
@Lattyware,我不理解你上一条评论。你在说什么? - eyquem
列表推导式生成一个新的列表这一事实是无关紧要的——因为没有用例需要引用旧列表。 - Gareth Latty
@Lattyware 永远不知道。这取决于代码使用者的目的。我们不知道,所以我们必须停止做出假设。 - eyquem
@Lattyware 谁知道如果这个类被用作继承中的基类会发生什么。 - eyquem
显示剩余4条评论

1

类本身在类块执行完成之后才被定义,因此您无法在其自身定义中使用该类。

您可以使用类装饰器或元类在创建类后添加所需的类变量。以下是一个使用装饰器的示例。

def addClassy(cls):
    cls.CLASSIES = [cls() for a in xrange(4)]
    return cls

@addClassy
class Classy(object):
    pass

>>> Classy.CLASSIES
0: [<__main__.Classy object at 0x000000000289A240>,
 <__main__.Classy object at 0x000000000289A518>,
 <__main__.Classy object at 0x000000000289A198>,
 <__main__.Classy object at 0x000000000289A208>]

虽然这样做可以实现功能,但感觉有点不对劲——我认为这使得更难看出该类具有CLASSIES属性以及其中包含的内容。 - Gareth Latty
1
@Lattyware:这取决于您需要多频繁地执行此操作。如果您需要在多个类中使用此模式,则创建一个装饰器来执行此操作,而不是手动为每个类重复编写代码,这样做是有意义的。 - BrenBarn
在那种情况下,是更有意义的选择,没错。 - Gareth Latty
@BrenBarn 我没有通过执行代码来验证,但我觉得 addClassy( ) 会向类添加属性 CLASSIES,即使它已经存在于其中(这不是你的代码的情况)。在某些情况下,列表不发生变化可能很重要。请参见我的答案中的编辑,你的代码在这一点上与 Lattyware 的代码相似。 - eyquem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接