2013-10-08 47 views
8

它看起来像thisthis有些相关的线程,但仍然没有想通的事情了:)无法为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 

经过一番打探(例如,见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' 

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

+0

为什么你有双'__init__'和'__new__'方法?只有第二个是重要的,它会覆盖第一个。 Python不会“重载”方法签名。 –

+0

没有重载...所以这意味着我以不同方式创建C实例的初始目标(取决于重载的构造函数)实际上不可行? – Jens

+0

这是完全可行的,只是使用不同的范例。 –

回答

18

命名元组是不可变的,所以你不能在__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 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) 
+0

谢谢Martijn。在不重载的情况下,我决定使用一个构造函数(接收'x'和'y')和第二个工厂方法(接收'obj')。不是很漂亮,也许我更喜欢C++风格的构造函数重载,但我认为这与我可以用Python做的一样多。 – Jens

1

两件事情:一,你没有真正得到多少出namedtuple这里,至于我可以告诉。所以也许你应该转换到一个普通的班级。此外,您不能重载

其次,其他的可能性可能与您的问题有所帮助:

Factory design pattern - 而不是把不同参数的构造函数,有一个类,需要不同类型的参数,并呼吁具有适当参数的构造函数,在对象之外。 recordtype - 一个可变的namedtuple,允许默认值,但也可以让你按你原先想要的方式编写你的子类。 bunch - 不完全是一个命名的元组,但可以让你创建有些随意的对象。

+0

感谢您的联系,这些都是很好的参考! – Jens

0

有一种解决方法来更改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") 
+1

这将创建指定元组的新实例,并且对指定元组的所有现有引用仍将访问原始元素,而不是新元素。因此,他们不会看到变化。 – Jens

+0

无法将新实例分配给旧变量吗? – chahuja

+0

如果您在运行时跟踪* all *变量指向原始元组,那么是的。 – Jens