无法为 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'

我哪里错了?如何创建具有多个构造函数或初始化器的子类?

原文由 Jens 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 599
2 个回答

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

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

请注意,因为 __new__ 是新实例的工厂方法,所以您需要 返回 新创建的实例。如果您不在 __new__ 方法中使用 return --- ,则默认返回值为 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 模块,例如,有几个这样的工厂方法让你创建不适合标准构造函数的对象。 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)

原文由 Martijn Pieters 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

原文由 Yonatan Simson 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题