我只是试图简化我的一个类,并引入了一些与flyweight设计模式相同风格的功能。

然而,我有点困惑,为什么__init__总是在__new__之后被调用。我没想到会这样。有人能告诉我为什么会发生这种情况,我如何才能实现这个功能吗?(除了将实现放在__new__中,这感觉相当粗糙。)

这里有一个例子:

class A(object):
    _dict = dict()

    def __new__(cls):
        if 'key' in A._dict:
            print "EXISTS"
            return A._dict['key']
        else:
            print "NEW"
            return super(A, cls).__new__(cls)

    def __init__(self):
        print "INIT"
        A._dict['key'] = self
        print ""

a1 = A()
a2 = A()
a3 = A()

输出:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

Why?


当前回答

__new__是静态类方法,而__init__是实例方法。 __new__必须先创建实例,所以__init__可以初始化它。注意,__init__以self作为参数。在你创造实例之前,没有自我。

现在,我猜想,您正在尝试用Python实现单例模式。有几种方法可以做到这一点。

此外,从Python 2.6开始,您可以使用类装饰器。

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

其他回答

__new__应该返回一个新的类的空白实例。然后调用__init__来初始化该实例。你没有在__new__的“NEW”情况下调用__init__,所以它是为你调用的。调用__new__的代码不会跟踪__init__是否在特定实例上被调用,也不应该跟踪,因为你在这里做了一些非常不寻常的事情。

你可以在__init__函数中为对象添加一个属性,以表明它已被初始化。检查该属性是否作为__init__中的第一个属性存在,如果已经存在,则不要继续进行任何操作。

原因很简单,new用于创建实例,而init用于初始化实例。在初始化之前,应该先创建实例。这就是为什么new应该在init之前调用。

然而,我有点困惑,为什么__init__总是在__new__之后被调用。

我认为c++的类比在这里是有用的:

__new__只是为对象分配内存。对象的实例变量需要内存来保存它,这就是步骤__new__要做的。 __init__将对象的内部变量初始化为特定的值(可以是默认值)。

@AntonyHatchkins回答的更新,您可能需要为元类型的每个类创建一个单独的实例字典,这意味着您应该在元类中使用__init__方法来使用该字典初始化类对象,而不是使它在所有类中都是全局的。

class MetaQuasiSingleton(type):
    def __init__(cls, name, bases, attibutes):
        cls._dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print('EXISTS')
            instance = cls._dict[key]
        else:
            print('NEW')
            instance = super().__call__(key)
            cls._dict[key] = instance
        return instance

class A(metaclass=MetaQuasiSingleton):
    def __init__(self, key):
        print 'INIT'
        self.key = key
        print()

我已经继续使用__init__方法更新了原始代码,并将语法更改为Python 3符号(在类参数中无参数调用super和元类,而不是作为属性)。

无论哪种方式,这里的重点是,如果找到键,您的类初始化器(__call__方法)将不会执行__new__或__init__。这比使用__new__干净得多,如果你想跳过默认的__init__步骤,则需要标记对象。

我认为这个问题的简单答案是,如果__new__返回一个与类类型相同的值,__init__函数将执行,否则它不会执行。在这种情况下,您的代码返回A._dict('key'),它与cls是同一个类,因此将执行__init__。