确保__init__仅在构造函数或__new__创建类实例时调用一次

时间:2011-12-26 07:58:53

标签: python

我正在尝试理解如何在创建过程可以通过构造函数或通过__new__方法创建Python类的新实例。特别是,我注意到在使用构造函数时,__init__方法将在__new__之后自动调用,而在直接调用__new__时,__init__类将不会被自动调用。我可以通过在__init__内嵌入对__new__的调用来明确调用__init__时强制调用__new__,但__init__最终会被调用两次当通过构造函数创建类时。

例如,考虑以下玩具类,它存储一个内部属性,即名为list的{​​{1}}对象:将此视为向量类的开头很有用。

data

可以使用构造函数创建类的新实例(实际上不确定这是否是Python中的正确术语),例如

class MyClass(object): def __new__(cls, *args, **kwargs): obj = object.__new__(cls, *args, **kwargs) obj.__init__(*args, **kwargs) return obj def __init__(self, data): self.data = data def __getitem__(self, index): return self.__new__(type(self), self.data[index]) def __repr__(self): return repr(self.data)

或通过切片,您可以在x = MyClass(range(10))方法中调用__new__来调用。

__getitem__

在第一个实例中,x2 = x[0:2]将被调用两次(通过__init__中的显式调用然后再自动调用),并且在第二个实例中调用一次。显然,我只想在任何情况下调用__new__一次。有没有一种标准的方法在Python中执行此操作?

请注意,在我的示例中,我可以摆脱__init__方法并将__new__重新定义为

__getitem__

但如果我以后想要继承def __getitem__(self, index): return MyClass(self.data[index]) ,这会导致问题,因为如果我拨打MyClass这样的电话,我会找回child_instance[0:2]的实例,而不是儿童班。

3 个答案:

答案 0 :(得分:10)

首先,关于__new____init__的一些基本事实:

  • __new__构造函数
  • __new__通常返回cls的第一个参数的实例。
  • __new__返回cls__new__ causes Python to call __init__
  • 的实例
  • __init__初始值设定项。它修改了实例(self) 由__new__返回。它不需要返回self

MyClass定义:

def __new__(cls, *args, **kwargs):
    obj = object.__new__(cls, *args, **kwargs)
    obj.__init__(*args, **kwargs)
    return obj

MyClass.__init__被调用两次。在明确调用obj.__init__之后,第二次因为__new__返回obj而调用cls的实例。 (由于object.__new__的第一个参数是cls,因此返回的实例是MyClass的实例,因此obj.__init__调用MyClass.__init__,而不是object.__init__。 )


Python 2.2.3 release notes有一个有趣的评论,可以说明何时使用__new__以及何时使用__init__

  

调用__new__方法,将类作为其第一个参数;它的   责任是返回该类的新实例。

     

将此与__init__进行比较:调用__init__作为其实例   第一个参数,它不返回任何东西;它的责任是   初始化实例。

     

所有这一切都是为了使不可变类型可以保留它们   允许子类化的不变性。

     

不可变类型(int,long,float,complex,str,unicode和   元组)有一个虚拟__init__,而可变类型(字典,列表,   文件,还有super,classmethod,staticmethod和property)都有   假__new__

因此,使用__new__来定义不可变类型,并使用__init__来定义可变类型。虽然可以定义两者,但您不需要这样做。


因此,由于MyClass是可变的,因此您应该只定义__init__

class MyClass(object):
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return type(self)(self.data[index])

    def __repr__(self):
        return repr(self.data)

x = MyClass(range(10))
x2 = x[0:2]

答案 1 :(得分:1)

有几件事情不应该做:

  • __init__
  • 致电__new__
  • 直接在方法
  • 中致电__new__

正如您已经看到的,在创建给定类的对象时,会自动调用__new____init__方法。直接使用它们会破坏此功能(但允许在另一个__init__内调用__init__,如下例所示。

您可以在获取__class__属性的任何方法中获取对象的类,如以下示例所示:

class MyClass(object):
    def __new__(cls, *args, **kwargs):
        # Customized __new__ implementation here
        return obj

    def __init__(self, data):
        super(MyClass, self).__init__(self)
        self.data = data

    def __getitem__(self, index):
        cls = self.__class__
        return cls(self.data[index])

    def __repr__(self):
        return repr(self.data)

x = MyClass(range(10))
x2 = x[0:2]

答案 2 :(得分:1)

使用MyClass(args)创建类的实例时,默认实例创建顺序如下:

  1. MyClass.__new__(args)被调用以获取新的“空白”实例
  2. 调用
  3. new_instance.__init__(args)new_instance是从上面调用__new__返回的实例),以初始化新实例的属性[1]
  4. new_instance作为MyClass(args)
  5. 的结果返回

    从此可以看出,自己调用MyClass.__new__而不是会导致调用__init__,因此您最终会得到一个未初始化的实例。同样明确的是,将__init__拨打__new__也不正确,因为MyClass(args)__init__ 两次致电

    问题的根源是:

      

    我正在尝试理解Python类的新实例应该如何   当创建过程可以通过构造函数或者创建时创建   通过方法

    创建过程通常不应该通过__new__方法。 __new__是普通实例创建协议的部分,因此您不应期望它为您调用整个协议。

    一个(坏的)解决方案是亲自手动实施此协议;而不是:

    def __getitem__(self, index):
        return self.__new__(type(self), self.data[index])
    
    你可以:

    def __getitem__(self, index):
        new_item = self.__new__(type(self), self.data[index])
        new_item.__init__(self.data[index])
        return new_item
    

    但实际上,你想要做的事情并不是完全混淆__new__。默认的__new__适用于您的情况,默认的实例创建协议适用于您的情况,因此您既不应该实现__new__也不应该直接调用它。

    你想要的是通过调用类来以正常方式创建类的新实例。如果没有继承,并且您认为没有继承,只需将self.__new__(type(self), self.data[index])替换为MyClass(self.data[index])

    如果您认为有一天可能是MyClass的子类想要通过切片而不是MyClass来创建子类的实例,那么您需要动态获取{{1}的类并调用它。你已经知道如何做到这一点,因为你在程序中使用它! self将返回type(self)的类型(类),然后您可以通过self MyClass直接调用它来完全调用。


    顺便说一句,type(self)(self.data[index])的意思是你想要在初始化之前自定义获取类的“新”空白实例的过程。几乎所有时间,这都是完全没必要的,默认__new__也没问题。

    在两种情况下,您只需要__new__

    1. 你有一个不寻常的“分配”方案,你可能会返回一个现有的实例,而不是创建一个真正的新实例(实际创建一个新实例的唯一方法是委托{{1]的最终默认实现无论如何)。
    2. 您正在实现不可变内置类型的子类。由于不可变的内置类型在创建后无法修改(因为它们是不可变的),因此必须将它们初始化为,因为它们是在__new__中创建的,而不是之后的。{/ li>

      作为point(1)的概括,你可以让__new__返回你喜欢的任何东西(不一定是类的实例),以便以某种任意奇怪的方式调用一个类。不过,这似乎总是让人感到困惑而不是有用。


      [1]我相信事实上协议稍微复杂一些; __init__仅在__new__返回的值上调用,如果它是为启动进程而调用的类的实例。然而,事实并非如此。