2013-10-08 16 views
8

Sembra this o this sono discussioni in qualche modo correlati, ma le cose ancora non ho capito :)Impossibile impostare l'attributo di sottoclassi di namedtuple

che sto cercando di creare una sottoclasse di namedtuple e fornire diversi inizializzatori in modo che io possa costruire oggetti in modi diversi. Ad esempio:

>>> 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 

Tuttavia, questo non funziona:

>>> 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 

Dopo qualche rovistando (per esempio, vedere this filo) Ho cercato di utilizzare costruttori anziché initializers:

>>> 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) 

che sembrava costruire un oggetto ma poi non riesco a leggere i suoi attributi:

>>> 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' 

Dove sto andando storto qui? Come posso creare una sottoclasse con più costruttori o inizializzatori?

+0

Perché hai i metodi doppio '__init__' e' __new__'? Solo il secondo conta, sovrascrive il primo. Python non esegue il "overload" delle firme dei metodi. –

+0

Nessun sovraccarico ... Questo significa che il mio obiettivo originale di creare istanze di C in modi diversi (a seconda dei costruttori sovraccaricati) non è effettivamente fattibile? – Jens

+0

È perfettamente fattibile, usando solo paradigmi diversi. –

risposta

18

Le tuple con nome sono immutabili, quindi non è possibile manipolarle nell'inizializzatore __init__. L'unica opzione è quella di sovrascrivere il metodo __new__:

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

Nota che a causa __new__ è un metodo di fabbrica per le nuove istanze, si ha bisogno di ritorno l'istanza appena creata. Se non si utilizza return nel metodo __new__, il valore di ritorno predefinito è None, che fornisce l'errore.

demo con un oggetto con x e y attributi:

>>> 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 non supporta sovraccarico metodo; generalmente si usano argomenti di parole chiave opzionali o metodi di classe extra come metodi di fabbrica.

Il datetime module, ad esempio, ha diversi metodi di fabbrica per consentire di creare oggetti che non si adattano al costruttore standard. datetime.datetime.fromtimestamp() crea un'istanza datetime.datetime da un singolo valore numerico e così anche datetime.datetime.fromordinal(); tranne che interpretano il numero in modi diversi.

Se si voleva sostenere argomenti variabili, fare:

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) 

Qui, y è un argomento opzionale, inadempiente a None se non fornito dal chiamante:

>>> C(3, 5): 
C(x=3, y=5) 
>>> C(O) 
C(x=10, y=20) 

L'alternativa, utilizzando un metodo di classe, sarebbe:

class C(namedtuple('C', 'x, y')): 
    @classmethod 
    def from_attributes(cls, obj): 
     return cls(obj.x, obj.y) 

Ora c'è un re due metodi di fabbrica; quello di default e uno di nome:

>>> C(3, 5): 
C(x=3, y=5) 
>>> C.from_attributes(O) 
C(x=10, y=20) 
+0

Grazie Martijn. Senza sovraccaricare ho deciso di usare un costruttore (che riceve 'x' e' y') e un secondo metodo factory (che riceve un 'obj'). Non carina, forse preferisco il sovraccarico del costruttore in stile C++, ma credo che sia quanto posso fare con Python. – Jens

1

due cose: uno, non sei davvero ottenere molto di namedtuple qui, per quanto posso dire. Quindi forse dovresti passare a una classe normale. Inoltre, non è possibile sovraccaricare i

secondo luogo, altre possibilità che potrebbero contribuire con il tuo problema:

Factory design pattern - invece di mettere i diversi parametri nel costruttore, avere una classe che richiede diversi tipi di parametri e chiamate il costruttore con argomenti appropriati, al di fuori dell'oggetto. recordtype - un namedtuple mutabile, che consente i valori predefiniti ma consente anche di scrivere la sottoclasse nel modo desiderato. bunch - non esattamente una tupla con nome, ma consente di creare oggetti un po 'arbitrari.

+0

Grazie per i collegamenti, questi sono buoni riferimenti! – Jens

0

C'è una soluzione alternativa per cambiare l'attributo di un 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

Questo crea una nuova istanza della tupla con nome, e tutti i riferimenti esistenti alla tupla nominata accederanno comunque all'originale e non a quella nuova. Quindi, non vedranno il cambiamento. – Jens

+0

Non è possibile assegnare la nuova istanza alla vecchia variabile? – chahuja

+0

Se si tiene traccia delle * tutte * variabili in fase di runtime che puntano alla tupla originale, allora sì. – Jens

Problemi correlati