从 namedtuple 继承的 类 如何使用重新定义的 __init__ 维护对父属性的访问?

How do classes inherited from namedtuple maintain access to parent properties with a redefined __init__?

最小可重现示例:

from collections import namedtuple

class Test(namedtuple('Test', ['a','b'])):
           def __init__(self, a, b):
                self.c = self.a + self.b
           def __str__(self):
                return self.c

print(Test('FIRST', 'SECOND'))

输出:

FIRSTSECOND

我认为当定义一个 __init__ 函数时,它会覆盖父实现。如果是这种情况,self.aself.b 如何存在正确的值?如果我放弃 __init__ 中的 ab 参数,我会得到 TypeError: __init__() takes 1 positional argument but 3 were given。我需要提供参数,但它们也没有在 __init__ 中明确设置,而且我没有调用 super().

self.aself.b 在调用 __init__ 之前由命名元组的 __new__ 方法设置。这是因为命名元组是不可变的(除了添加附加属性的能力,如 Test.__init__ 所做的),因此尝试设置 ab after 创建元组会失败。相反,这些值会传递给 __new__,以便在创建元组时可以使用这些值。

这是一个 __new__ 被覆盖以交换 ab 值的示例。

class Test(namedtuple('Test', ['a','b'])):
    def __new__(cls, a, b, **kwargs):
        return super().__new__(cls, b, a, **kwargs)

    def __init__(self, a, b):
        self.c = self.a + self.b

    def __str__(self):
        return self.c

print(Test('FIRST', 'SECOND'))  # outputs SECONDFIRST

尝试对 __init__ 做同样的事情会失败:

class Test(namedtuple('Test', ['a','b'])):
    def __init__(self, a, b):
        self.a, self.b = b, a
        self.c = self.a + self.b

    def __str__(self):
        return self.c

print(Test('FIRST', 'SECOND'))  # outputs SECONDFIRST

结果

Traceback (most recent call last):
  File "/Users/chepner/advent-of-code-2020/tmp.py", line 11, in <module>
    print(Test('FIRST', 'SECOND'))
  File "/Users/chepner/advent-of-code-2020/tmp.py", line 5, in __init__
    self.a, self.b = b, a
AttributeError: can't set attribute

要使 c 也保持不变(同时保持它与元组本身不同),请使用 属性.

class Test(namedtuple('Test', ['a','b'])):
    @property
    def c(self):
        return self.a + self.b

    def __str__(self):
        return self.c

请注意,将 Test 的实例视为常规元组时,c 不可见或不可访问:

>>> x = Test("First", "Second")
>>> x
Test(a='First', b='Second')
>>> len(x)
2
>>> tuple(x)
('First', 'Second')
>>> x[0]
'First'
>>> x[1]
'Second'
>>> x[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
>>> x.c = "foo"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute