如何初始化需要__new__和__init__的对象

时间:2018-09-09 01:36:39

标签: python

我正在创建一个类sequence,该类继承自内置的list,并将保存第二个类的有序集合:d0,该类继承自intd0除其int值外,还必须包含一个辅助值i,该值指示其在类中的位置以及对类本身的引用。

我的理解是因为int是不可变的类型,我必须使用__new__方法,并且由于它具有其他属性,因此我需要使用__init__

我一直在努力使它起作用,我已经探索了一些选择。

尝试1:

class sequence(list):
    def __init__(self, data):
        for i, elem in enumerate(data): self.append( d0(elem, i, self) )

class d0(int):
    def __new__(self, val, i, parent):
        self.i = i
        self.parent = parent
        return int.__new__(d0, val)

x = sequence([1,2,3])
print([val.i for val in x])

这对我来说是最直观的,但是每次分配self.i时,它都会覆盖id0的所有其他实例的sequence属性。尽管我不清楚为什么会发生这种情况,但我知道__new__并不是实例化对象的地方。

尝试2:

class sequence(list):
    def __init__(self, data):
        for i, val in enumerate(data): self.append( d0(val, i, self) )

class d0(int):
    def __new__(cls, *args):
        return super().__new__(cls, *args)

    def __init__(self, *args):
        self = args[0]
        self.i = args[1]
        self.parent = args[2]

x = sequence([1,2,3])
print([val.i for val in x])

这引发了TypeError: int() takes at most 2 arguments (3 given),尽管我不确定为什么。

尝试3:

class sequence(list):
    def __init__(self, data):
        for i, val in enumerate(data):
            temp = d0.__new__(d0, val)
            temp.__init__(i, self)
            self.append(temp)

class d0(int):
    def __new__(cls, val):
        return int.__new__(d0, val)

    def __init__(self, i, parent):
        self.i = i
        self.parent = parent

x = sequence([1,2,3])
print([val.i for val in x])

这可以完成任务,但是很麻烦,否则必须显式调用__new____init__实例化一个对象就感到很奇怪。

完成此操作的正确方法是什么?我也希望对尝试1和2中的不良行为做出任何解释。

1 个答案:

答案 0 :(得分:1)

首先,您的sequence到目前为止还不是很多类型:对其调用append不会保留其索引性质(更不用说sort或切片分配!)。如果您只想制作如下所示的列表,则只需编写一个函数,该函数返回一个list。请注意,list本身的行为就像这样的函数(在Python 1天里它就!),因此您仍然经常可以像类型一样使用它。

因此,让我们谈谈d0。抛开从int衍生是否是一个好主意的问题(至少比正确地从list衍生的工作少!),您有一个正确的基本想法:您需要 __new__表示不可变(基本)类型,因为在__init__时,选择其值为时已晚。这样做:

class d0(int):
  def __new__(cls,val,i,parent):
    return super().__new__(cls,val)

请注意,这是一个 class 方法:尚无实例,但是我们确实需要知道我们要实例化哪个类(如果有人继承自d0,该怎么办?)。这是尝试#1 错误的原因:它认为第一个参数是为其分配属性的实例。

请注意,我们仅传递一个(其他)参数:int无法使用我们的辅助数据。 (也不能忽略它:考虑int('f',16)。)因此失败了#2 :它把所有参数都发送了。

我们现在可以安装其他属性,但是正确的做法是使用__init__制造对象与初始化分开

# d0 continued
  def __init__(self,val,i,parent):
    # super().__init__(val)
    self.i=i; self.parent=parent

请注意,所有参数都将再次出现,甚至val也被我们忽略。这是因为调用一个类仅涉及一个参数列表( cf。 d0(elem,i,self)),因此__new____init__必须共享它。 (因此,将val传递给int.__init__在形式上是正确的,但是它会做什么呢?因为我们知道int已经完全设置好了,所以调用它根本没有用。 。)使用#3 很痛苦,因为它没有遵循此规则。