51

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

我正在尝试创建一个子类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'

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

4

4 回答 4

49

命名元组是不可变的,因此您不能在__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模块有几个这样的工厂方法可以让您创建不适合标准构造函数的对象。从单个数值datetime.datetime.fromtimestamp()创建一个实例,也是如此;除了他们以不同的方式解释这个数字。datetime.datetimedatetime.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)
于 2013-10-08T20:27:51.873 回答
35

我建议你使用_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)
于 2018-12-10T16:10:01.500 回答
6

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

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

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

于 2013-10-08T20:57:47.873 回答
3

有一种解决方法可以更改命名元组的属性。

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")
于 2016-06-21T08:11:23.953 回答