Наследование от базового класса namedtuple

Этот вопрос задает противоположность Наследовать namedtuple от базового класса в python , где целью является наследование подкласса от namedtuple, а не наоборот.

В обычном наследовании это работает:

class Y(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c


class Z(Y):
    def __init__(self, a, b, c, d):
        super(Z, self).__init__(a, b, c)
        self.d = d

[вне]:

>>> Z(1,2,3,4)
<__main__.Z object at 0x10fcad950>

Но если базовым классом является namedtuple:

from collections import namedtuple

X = namedtuple('X', 'a b c')

class Z(X):
    def __init__(self, a, b, c, d):
        super(Z, self).__init__(a, b, c)
        self.d = d

[вне]:

>>> Z(1,2,3,4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() takes exactly 4 arguments (5 given)

Вопрос: возможно ли наследовать namedtuples как базовый класс в Python? Как?


person alvas    schedule 22.02.2017    source источник
comment
Это не совсем ответ на ваш вопрос, но, возможно, стоит проверить новый python классы данных. В большинстве случаев, когда вы переопределяете namedtuple, вместо этого вы можете использовать их.   -  person ethanabrooks    schedule 21.06.2019


Ответы (4)


Вы можете, но вам нужно переопределить __new__, который неявно вызывается перед __init__:

class Z(X):
  def __new__(cls, a, b, c, d):
    self = super(Z, cls).__new__(cls, a, b, c)
    self.d = d
    return self

>>> z = Z(1, 2, 3, 4)
>>> z
Z(a=1, b=2, c=3)
>>> z.d
4

Но d будет просто независимым атрибутом!

>>> list(z)
[1, 2, 3]
person schwobaseggl    schedule 22.02.2017
comment
Могу ли я сделать self.append(d)? - person alvas; 22.02.2017
comment
@alvas Нет, потому что namedtuple в основном является tuple (неизменяемым по дизайну и, следовательно, не имеет append) - person MSeifert; 22.02.2017
comment
@Mseifert Спасибо! Но =( - person alvas; 22.02.2017
comment
Является ли наследование от namedtuple анти-шаблоном или это разумный подход, если мне просто нужны дополнительные аргументы-купе, которые я хотел бы отделить от остальных? (В частности, я хочу, чтобы они вычислялись в __init__, а не назначались в пользовательском коде.) - person max; 09.05.2017

Я думаю, вы можете добиться того, чего хотите, включив все поля в исходный именованный кортеж, а затем отрегулировав количество аргументов, используя __new__, как предлагает schwobaseggl выше. Например, для случая max, когда некоторые входные значения должны быть рассчитаны, а не переданы напрямую, работает следующее:

from collections import namedtuple

class A(namedtuple('A', 'a b c computed_value')):
    def __new__(cls, a, b, c):
        computed_value = (a + b + c)
        return super(A, cls).__new__(cls, a, b, c, computed_value)

>>> A(1,2,3)
A(a=1, b=2, c=3, computed_value=6)
person TimH    schedule 19.07.2017

Я пришел сюда с точно такой же проблемой, всего два года спустя.
Лично мне показалось, что декоратор @property сюда больше подходит:

from collections import namedtuple

class Base:
    @property
    def computed_value(self):
        return self.a + self.b + self.c

# inherits from Base
class A(Base, namedtuple('A', 'a b c')):
    pass

cls = A(1, 2, 3)
print(cls.computed_value)
# 6
person Jan    schedule 05.05.2019
comment
Теперь это упоминается в документации: docs.python.org/3 /библиотека/ - person GGGuser; 03.11.2020

Вместо того, чтобы думать строго с точки зрения наследования, поскольку namedtuple — это функция, другим подходом может быть инкапсуляция его в новую функцию.

Тогда возникает вопрос: как создать именованный кортеж со свойствами a, b, c по умолчанию и, возможно, некоторыми дополнительными?

def namedtuple_with_abc(name, props=[]):
     added = props if type(props) == type([]) else props.split()
     return namedtuple(name, ['a', 'b', 'c'] + added)

X = namedtuple_with_abc('X')
Z = namedtuple_with_abc('Z', 'd e')

>>> X(1, 2, 3)
X(a=1, b=2, c=3)

>>> Z(4, 5, 6, 7, 8)
Z(a=4, b=5, c=6, e=7, f=8)
person Miklos Aubert    schedule 24.05.2021