2013-10-08 49 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