Невозможно установить атрибут для подклассов namedtuple
Похоже, этот или этот - несколько связанных потоков, но до сих пор не понял:)
Я пытаюсь создать подкласс namedtuple
и предоставить разные инициализаторы, чтобы я мог строить объекты по-разному. Например:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __init__(self, obj) : # Initialize a C instance by copying values from obj
... self.x = obj.a
... self.y = obj.b
... def __init__(self, x, y) : # Initialize a C instance from the parameters
... self.x = x
... self.y = y
Однако это не работает:
>>> c = C(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __init__
AttributeError: can't set attribute
После некоторого прокрутки (например, см. этот поток) Я попытался использовать конструкторы вместо инициализаторов:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __new__(cls, obj) :
... self = super(C, cls).__new__(cls, obj.a, obj.b)
... def __new__(cls, x, y) :
... self = super(C, cls).__new__(cls, x, y)
который, казалось, создал объект, но затем я не могу прочитать его атрибуты:
>>> c = C(1,2)
>>> c.x, c.y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'x'
Где я здесь ошибаюсь? Как создать подкласс с несколькими конструкторами или инициализаторами?
Ответы
Ответ 1
Именованные кортежи неизменяемы, поэтому вы не можете манипулировать ими в инициализаторе __init__
. Единственный вариант - переопределить метод __new__
:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, obj):
return super(C, cls).__new__(cls, obj.x, obj.y)
Обратите внимание, что поскольку __new__
- это метод factory для новых экземпляров, вам нужно вернуть вновь созданный экземпляр. Если вы не используете return
в методе __new__
, возвращаемое по умолчанию значение None
, что дает вам вашу ошибку.
Демо с объектом с атрибутами x
и y
:
>>> class C(namedtuple('C', 'x, y')):
... __slots__ = ()
... def __new__(cls, obj):
... return super(C, cls).__new__(cls, obj.x, obj.y)
...
>>> O.x, O.y
(10, 20)
>>> C(O)
C(x=10, y=20)
Python не поддерживает перегрузку метода; обычно вы либо используете необязательные аргументы ключевого слова, либо дополнительные методы класса в качестве методов factory.
datetime
модуль, например, имеет несколько таких методов factory, которые позволяют создавать объекты, не соответствующие стандартным конструкторам. datetime.datetime.fromtimestamp()
создает экземпляр datetime.datetime
из одного числового значения, а также datetime.datetime.fromordinal()
; за исключением того, что они интерпретируют число по-разному.
Если вы хотите поддерживать переменные аргументы, выполните следующие действия:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, x, y=None):
if y is None:
# assume attributes
x, y = x.x, x.y
return super(C, cls).__new__(cls, x, y)
Здесь y
- необязательный аргумент, по умолчанию None
, если он не указан вызывающим:
>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)
Альтернативой, используя метод класса, будет:
class C(namedtuple('C', 'x, y')):
@classmethod
def from_attributes(cls, obj):
return cls(obj.x, obj.y)
Теперь существует два метода factory; один по умолчанию и один по имени:
>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)
Ответ 2
Две вещи: одна, вы на самом деле не получаете много от именованного здесь, насколько я могу судить. Так что, возможно, вам стоит просто переключиться на обычный класс. Кроме того, вы не можете перегрузить
Во-вторых, другие возможности, которые могут помочь в вашей проблеме:
Factory шаблон дизайна - вместо того, чтобы поместить различные параметры в конструктор, введите класс, который принимает различные параметры и вызывает конструктор с соответствующими аргументами, вне объекта.
recordtype - измененный namedtuple, который позволяет устанавливать значения по умолчанию, но также позволит вам написать свой подкласс так, как вы хотели.
bunch - не совсем именованный кортеж, но позволяет создавать несколько произвольных объектов.
Ответ 3
Существует способ обхода атрибута namedtuple.
import collections
def updateTuple(NamedTuple,nameOfNamedTuple):
## Convert namedtuple to an ordered dictionary, which can be updated
NamedTuple_asdict = NamedTuple._asdict()
## Make changes to the required named attributes
NamedTuple_asdict['path']= 'www.google.com'
## reconstruct the namedtuple using the updated ordered dictionary
updated_NamedTuple = collections.namedtuple(nameOfNamedTuple, NamedTuple_asdict.keys())(**NamedTuple_asdict)
return updated_NamedTuple
Tuple = collections.namedtuple("Tuple", "path")
NamedTuple = Tuple(path='www.yahoo.com')
NamedTuple = updateTuple(NamedTuple, "Tuple")
Ответ 4
Я предлагаю вам использовать метод _replace
from collections import namedtuple
C = namedtuple('C', 'x, y')
c = C(x=10, y=20)
# c.x = 30 won't work
c = c._replace(x=30)