2010-11-04 14 views
31

Sono interessato a utilizzare l'oggetto elenco python, ma con funzionalità leggermente alterata. In particolare, vorrei che l'elenco fosse 1-index anziché 0-indexed. Es .:Come ereditare ed estendere un oggetto lista in Python?

>> mylist = MyList() 
>> mylist.extend([1,2,3,4,5]) 
>> print mylist[1] 

uscita dovrebbe essere: 1

Ma quando ho cambiato le __getitem__() e __setitem__() metodi per fare questo, mi è stato sempre un errore di RuntimeError: maximum recursion depth exceeded. Ho armeggiato in giro con questi metodi molto, ma questo è fondamentalmente quello che avevo in là:

class MyList(list): 
    def __getitem__(self, key): 
     return self[key-1] 
    def __setitem__(self, key, item): 
     self[key-1] = item 

Credo che il problema è che è essa stessa self[key-1] chiamando lo stesso metodo è definire. In tal caso, come faccio a utilizzare il metodo list() anziché il metodo MyList()? Ho provato a utilizzare super[key-1] invece di self[key-1] ma che ha provocato la denuncia TypeError: 'type' object is unsubscriptable

Qualche idea? Anche se potresti indicarmi un buon tutorial per questo sarebbe fantastico!

Grazie!

+8

Questo viola il Principio di sostituzione di Liskov in modo piuttosto evidente. Potrebbe non esserci molto valore nella sottoclasse di 'list' se non si può effettivamente usarlo da nessuna parte che ci si aspetterebbe un' elenco'. Forse la composizione sarebbe una strategia più appropriata in questo caso? – Ken

+0

Non capisco bene; cosa intendi per "composizione"? Inoltre, come possiamo essere sicuri di non poter sostituire MyList al posto di una lista standard? C'è un buon modo per dire? Ad esempio, se i metodi interni usano '__getitem__' e' __setitem__', allora ci sarebbe un problema? – mindthief

+2

mindthief: http://en.wikipedia.org/wiki/Composition_over_inheritance - e se cambi l'interfaccia esistente di 'list' (cioè, esattamente quello che stai chiedendo qui), allora non puoi sostituire un' MyList' per un 'elenco'. Anche le cose basilari come 'x [0]' non funzioneranno. – Ken

risposta

41

Utilizzare la funzione super() per chiamare il metodo della classe base, o richiamare il metodo direttamente:

o

class MyList(list): 
    def __getitem__(self, key): 
     return super(MyList, self).__getitem__(key-1) 

Tuttavia, questo non cambierà il comportamento di altri metodi delle liste . Ad esempio, l'indice rimane invariato, che può portare a risultati imprevisti:

numbers = MyList() 
numbers.append("one") 
numbers.append("two") 

print numbers.index('one') 
>>> 1 

print numbers[numbers.index('one')] 
>>> 'two' 
+2

Inoltre, fai attenzione agli altri metodi di elenco che si interrompono perché stai modificando un comportamento di indicizzazione che è una parte fondamentale degli oggetti elenco. –

+1

I'll +1 se si nota perché si verifica la ricorsione. Tu dai la soluzione senza spiegare il problema. –

+8

Perché la ricorsione si verifica è stata spiegata perfettamente nella domanda. –

21

Invece, sottoclasse intero utilizzando lo stesso metodo per definire tutti i numeri per essere meno uno da ciò che li impostato su. Ecco.

Scusa, ho dovuto. È come la battuta su Microsoft che definisce il buio come lo standard.

11

È possibile evitare la violazione del principio di sostituzione di Liskov creando una classe che eredita da collections.MutableSequence, che è una classe astratta. Sarebbe come:

class MyList(collections.MutableSequence): 
def __init__(self, l=[]): 
    if type(l) is not list: 
     raise ValueError() 

    self._inner_list = l 

def __len__(self): 
    return len(self._inner_list) 

def __delitem__(self, index): 
    self._inner_list.__delitem__(index - 1) 

def insert(self, index, value): 
    self._inner_list.insert(index - 1, value) 

def __setitem__(self, index, value): 
    self._inner_list.__setitem__(index - 1, value) 

def __getitem__(self, index): 
    return self._inner_list.__getitem__(index - 1) 

C'è un problema qui (anche se potrebbe essercene di più). Se indicizzare il tuo nuovo elenco come questo:

l = MyList() 
l[0] 

sarà effettivamente chiamare:

self._inner_list[-1] 

che vi porterà l'ultimo elemento. Quindi è necessario eseguire ulteriori verifiche nei metodi e assicurarsi di mantenere l'indicizzazione inversa, se si desidera avere quella funzionalità per il proprio elenco.

EDIT:

Ecco il nuovo codice, che credo non dovrebbe avere alcun problema.

def indexing_decorator(func): 

    def decorated(self, index, *args): 
     if index == 0: 
      raise IndexError('Indices start from 1') 
     elif index > 0: 
      index -= 1 

     return func(self, index, *args) 

    return decorated 


class MyList(collections.MutableSequence): 
    def __init__(self): 
     self._inner_list = list() 

    def __len__(self): 
     return len(self._inner_list) 

    @indexing_decorator 
    def __delitem__(self, index): 
     self._inner_list.__delitem__(index) 

    @indexing_decorator 
    def insert(self, index, value): 
     self._inner_list.insert(index, value) 

    @indexing_decorator 
    def __setitem__(self, index, value): 
     self._inner_list.__setitem__(index, value) 

    @indexing_decorator 
    def __getitem__(self, index): 
     return self._inner_list.__getitem__(index) 

    def append(self, value): 
     self.insert(len(self) + 1, value) 
+0

'x = MyList(); x.append (4); y = MyList(); print (y) 'Provalo. Ottieni risultati interessanti ... – zondo

+1

Quasi lì. Non dovresti regolare l'indice per 'insert'. Inoltre, prova a gestire il caso in cui l'indice == 0. –

+0

Sì, hai ragione. Ma poi l'indicizzazione non sarebbe coerente. Ho modificato il codice nella risposta un po '. Dovrebbe funzionare bene ora, penso. –