无法为namedtuple的子类设置属性

时间:2013-10-08 20:17:40

标签: python class python-3.x namedtuple

看起来thisthis是一些相关的线程,但仍未解决问题:)

我正在尝试创建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

经过一些讨论(例如,参见this线程)后,我尝试使用构造函数而不是初始化器:

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

我在哪里错了?如何创建具有多个构造函数或初始值设定项的子类?

4 个答案:

答案 0 :(得分:27)

命名元组是不可变的,因此您无法在__init__初始值设定项中对它们进行操作。您唯一的选择是覆盖__new__方法:

class C(namedtuple('C', 'x, y')):
    __slots__ = ()
    def __new__(cls, obj):
        return super(C, cls).__new__(cls, obj.x, obj.y)

请注意,因为__new__是新实例的工厂方法,所以您需要返回新创建的实例。如果您未在return方法中使用__new__,则默认返回值为None,这会为您提供错误。

使用xy属性的对象进行演示:

>>> 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不支持方法重载;通常,您可以使用可选的关键字参数或额外的类方法作为工厂方法。

例如,datetime module有几个这样的工厂方法,可以让你创建不符合标准构造函数的对象。 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)

现在有两种工厂方法;一个默认值和一个名为:

>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)

答案 1 :(得分:3)

两件事:其中一件事,据我所知,你在这里并没有真正从这个名字中得到很多。所以也许你应该切换到正常的课程。

也不能超载

其次,可能有助于解决问题的其他可能性:

Factory design pattern - 不是在构造函数中放置不同的参数,而是使用一个接受不同类型参数的类,并在对象外部使用适当的参数调用构造函数。 recordtype - 一个可变的namedtuple,允许默认值,但也可以让你按照原来想要的方式编写子类。 bunch - 不完全是一个命名元组,但允许你创建一些任意对象。

答案 2 :(得分:3)

我建议您使用_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)

答案 3 :(得分:1)

有一种解决方法可以更改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")