Как классы, унаследованные от namedtuple, поддерживают доступ к родительским свойствам с переопределенным __init__ ?

#python #inheritance #namedtuple

#питон #наследование #названный корень

Вопрос:

Минимальный воспроизводимый пример:

 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.a они существуют и self.b существуют с правильными значениями? Если я откажусь a b от параметров и __init__ , я получу TypeError: __init__() takes 1 positional argument but 3 were given . Мне нужно предоставить параметры, но они не задаются явно ни в __init__ том, ни в другом, и у меня нет вызова super() .

Комментарии:

1. Потому self.a что и self.b не являются атрибутами. Базовая tuple структура создается внутри tuple.__new__ . Честно говоря, то, что у вас есть выше, не должно быть именованным кортежем для начала, может быть, просто dataclass, если вы хотите избежать шаблонности? Потому что вышесказанное сводит на нет преимущество экономии места namedtuple полностью

2. Мне нужен неизменяемый тип данных с определенным пользователем __str__ . Я подумал namedtuple , что лучшим способом сделать это будет выделение подклассов из

3. namedtuple это не класс, вы не можете создавать из него подклассы. это фабрика классов . namedtuple('Test', ['a','b']) возвращает тип, tuple подкласс. Но то, как вы это сделали здесь, вы сделали свой namedtuple изменяемым для загрузки! (обратите внимание, это должно быть, поскольку вы делаете self.c = self.a self.b . Опять же, возможно, рассмотрим dataclasses.dataclass

4. @juanpa.arrivillaga Я не думаю, что класс полностью изменяемый. в настоящее время c атрибут является изменяемым, a а b атрибуты and остаются неизменяемыми. Я хотел c бы также быть неизменяемым. Я посмотрю на dataclasses.dataclass спасибо.

5. Правильно. «не полностью изменяемый» для меня все еще означает «изменяемый». В любом случае, стремление к строгой неизменности в Python, помимо использования basic namedtuple , обычно не стоит усилий и практически всегда может быть легко нарушено, если вы не напишете C-расширение

Ответ №1:

self.a и self.b задаются методом именованного кортежа __new__ перед __init__ вызовом. Это связано с тем, что именованный кортеж является неизменяемым (помимо возможности добавлять дополнительные атрибуты, как Test.__init__ и делает), поэтому попытка установить a и b после создания кортежа завершится неудачей. Вместо этого значения передаются __new__ таким образом, чтобы значения были доступны при создании кортежа.

Вот пример __new__ переопределения для замены значений a и. b

 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
 

Обратите внимание, что c это не отображается или не доступно при обработке экземпляра Test как обычного кортежа:

 >>> 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
 

Комментарии:

1. Если бы я хотел c также быть неизменяемым, было бы это тем, что я установил __new__ и отказался бы от определения __init__ ?

2. @ajoseps вам нужно было бы использовать namedtuple('Test', ['a','b','c']) тогда, и, возможно, __new__ просто сделать что-то вроде return super().__new__(cls, a, b, a b)

3. Нет; тип, возвращаемый namedtuple является подклассом tuple ; определенные им атрибуты являются «особенными» не потому, что они инициализируются с помощью __new__ , а из-за того, как тип определен в первую очередь. Если вы хотите c , чтобы он был доступен только для чтения, лучше всего было бы сделать его свойством без установщика.

4. (Я могу представить случай, когда вы хотите различать значения, которые являются частью кортежа, и значения, которые являются атрибутами кортежа, даже если именованный кортеж разрешает доступ к обоим, используя один и тот же синтаксис.)

5. да, я хочу иметь возможность указать экземпляр типа данных с 2 параметрами и иметь 3-е свойство, производное от 2, но неизменяемое. Я бы хотел избежать явного указания 3 свойств в a namedtuple , поскольку это может привести к явному указанию третьего.