无法为namedtuple的子类设置属性

Jen*_*ens 27 python class namedtuple python-3.x

它看起来像这个这个有点相关的线程,但仍然没有弄清楚:)

我正在尝试创建一个子类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
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用:

>>> 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
Run Code Online (Sandbox Code Playgroud)

经过一番探讨(例如,看到这个帖子),我尝试使用构造函数而不是初始化器:

>>> 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)
Run Code Online (Sandbox Code Playgroud)

它似乎构造了一个对象,但后来我无法读取它的属性:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*ers 32

命名元组是不可变的,因此您无法在__init__初始化程序中操作它们.您唯一的选择是覆盖该__new__方法:

class C(namedtuple('C', 'x, y')):
    __slots__ = ()
    def __new__(cls, obj):
        return super(C, cls).__new__(cls, obj.x, obj.y)
Run Code Online (Sandbox Code Playgroud)

请注意,因为__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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

这里y是一个可选参数,None如果调用者没有提供,则默认为:

>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)
Run Code Online (Sandbox Code Playgroud)

使用类方法的替代方法是:

class C(namedtuple('C', 'x, y')):
    @classmethod
    def from_attributes(cls, obj):
        return cls(obj.x, obj.y)
Run Code Online (Sandbox Code Playgroud)

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

>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)
Run Code Online (Sandbox Code Playgroud)

  • @YonatanSimson:使用它怎么样?你能详细说明一下吗?请注意,`instance._replace()` 返回一个新实例,原始实例保持不变。`._replace()` 不会使工厂函数更加高效。 (2认同)
  • @YonatanSimson:你会如何在这里使用它?用户请求一个工厂方法来创建一个新对象。`_replace()` 在*现有实例*上效果很好,但这不是这里所要求的。 (2认同)

Yon*_*son 7

我建议您使用该_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)
Run Code Online (Sandbox Code Playgroud)

  • 这保留了命名元组背后的不可变性质和逻辑。更好的解决方案 (2认同)

Cor*_*man 6

两件事:第一,据我所知,你并没有真正从这里的 namedtuple 中得到多少。所以也许你应该切换到普通班级。此外,你不能超载

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

工厂设计模式- 不是将不同的参数放在构造函数中,而是拥有一个类,该类采用不同类型的参数并在对象外部使用适当的参数调用构造函数。 recordtype - 一个可变的命名元组,它允许默认值,但也可以让您按照最初想要的方式编写子类。 一堆- 不完全是一个命名元组,但可以让你创建一些任意的对象。