6

Quando si utilizza di lista o la parola chiave in in una per il contesto ciclo, vale a dire:Python: il meccanismo alla base di lista

for o in X: 
    do_something_with(o) 

o

l=[o for o in X] 
  • Come funziona il meccanismo dietro in opere ?
  • Quali funzioni \ metodi all'interno di X chiama?
  • Se X può essere conforme a più di un metodo, qual è la precedenza?
  • Come scrivere un efficiente X, in modo che la comprensione delle liste sia veloce?
+2

Si noti che la parola chiave "in" viene utilizzata in due contesti distinti in Python. Esistono iterazioni (con la tastiera "for") e contesti booleani/condizionali (a volte con "if" o "while"). Quest'ultimo chiama i metodi __taintain__ per i suoi oggetti. –

risposta

9

La risposta, completa e corretta.

for, sia per loop che per list comprehensions, chiamate iter() su X. iter() restituirà un iterable se X ha un metodo __iter__ o un metodo __getitem__. Se implementa entrambi, viene utilizzato __iter__. Se non hai né tu ottieni TypeError: 'Nothing' object is not iterable.

Questo implementa una __getitem__:

class GetItem(object): 
    def __init__(self, data): 
     self.data = data 

    def __getitem__(self, x): 
     return self.data[x] 

utilizzati:

>>> data = range(10) 
>>> print [x*x for x in GetItem(data)] 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

Questo è un esempio di attuazione __iter__:

class TheIterator(object): 
    def __init__(self, data): 
     self.data = data 
     self.index = -1 

    # Note: In Python 3 this is called __next__ 
    def next(self): 
     self.index += 1 
     try: 
      return self.data[self.index] 
     except IndexError: 
      raise StopIteration 

    def __iter__(self): 
     return self 

class Iter(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     return TheIterator(data) 

utilizzati:

>>> data = range(10) 
>>> print [x*x for x in Iter(data)] 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

Come si vede, è necessario entrambi implementare un iteratore e __iter__ che restituisce l'iteratore.

Si possono combinare:

class CombinedIter(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     self.index = -1 
     return self 

    def next(self): 
     self.index += 1 
     try: 
      return self.data[self.index] 
     except IndexError: 
      raise StopIteration 

Usage:

>>> well, you get it, it's all the same... 

Ma allora si può avere un solo iteratore di andare in una sola volta. OK, in questo caso si può solo fare questo:

class CheatIter(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     return iter(self.data) 

ma questo è barare, perché si sta solo riutilizzando il metodo di list__iter__. Un modo più semplice è quello di utilizzare la resa, e fare __iter__ in un generatore:

class Generator(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     for x in self.data: 
      yield x 

Quest'ultimo è il modo in cui mi sento di raccomandare. Facile ed efficiente

+0

+1 o più se potessi. Questa è una risposta favolosa. Grazie! –

+0

+1 Ottima risposta completa. Grazie anche per tutti i commenti. –

2

Forse questo aiuta (esercitazione http://docs.python.org/tutorial/classes.html Sezione 9.9):

Dietro le quinte, l'istruzione FOR chiamate ITER() sull'oggetto contenitore. La funzione restituisce un oggetto iteratore che definisce il metodo next() che accede agli elementi nel contenitore uno alla volta. Quando non ci sono più elementi, next() genera un'eccezione StopIteration che indica a il ciclo for da terminare.

5

X deve essere iterabile. Deve implementare __iter__() che restituisce un oggetto iteratore; l'oggetto iteratore deve implementare next(), che restituisce l'elemento successivo ogni volta che viene chiamato o genera un valore StopIteration se non c'è un elemento successivo.

Elenchi, tuple e generatori sono tutti iterabili.

Si noti che l'operatore normale for utilizza lo stesso meccanismo.

+0

Gli iteratori devono anche implementare '__iter __()', sebbene possano semplicemente restituire un riferimento a se stessi – Cameron

+0

Giusto, i generatori fanno proprio questo. – 9000

+1

X può implementare invece '__getitem__'. –

3

X deve essere un oggetto iterabile, ovvero deve avere un metodo __iter__().

Quindi, per avviare un ciclo for..in o una comprensione di lista, il primo metodo viene chiamato per ottenere un oggetto iteratore; quindi viene chiamato il metodo next() di quell'oggetto per ciascuna iterazione finché non viene generato StopIteration, a quel punto l'iterazione si interrompe.

Non sono sicuro di quale sia la terza domanda e come fornire una risposta significativa alla quarta domanda, tranne per il fatto che l'iteratore non deve costruire l'intera lista in memoria contemporaneamente.

+0

No, deve avere '__iter __()' o '__geitem __()'. –

+0

@Lennart: Grazie per avermi sistemato. –

0

per rispondere alle vostre domande:

Come funziona il meccanismo che sta dietro a opere?

È lo stesso meccanismo utilizzato per i cicli ordinari, come altri hanno già notato.

Quali funzioni \ metodi in X chiama?

Come indicato in un commento qui sotto, chiama iter(X) per ottenere un iteratore. Se X ha una funzione metodo __iter__() definita, questa verrà richiamata per restituire un iteratore; altrimenti, se X definisce __getitem__(), questo verrà chiamato ripetutamente per iterare su X. Vedere la documentazione di Python per

Se X può essere conforme a più di un metodo, qual è la precedenza?

Non sono sicuro di quale sia la tua domanda qui, esattamente, ma Python ha regole standard per come risolve i nomi dei metodi, e sono seguiti qui. Ecco una discussione di questo:

Method Resolution Order (MRO) in new style Python classes

come scrivere un X efficiente, in modo che di lista sarà veloce?

Ti suggerisco di leggere di più su iteratori e generatori in Python. Un modo semplice per rendere qualsiasi iterazione di supporto classe è creare una funzione generatore per iter(). Ecco una discussione di generatori:

http://linuxgazette.net/100/pramode.html

+0

Non proprio, chiama 'iter()' che chiama '__iter __()' o restituisce un iteratore che usa '__getitem__'. –

4

Rispondere a commenti di domanda posso dire che la lettura di sorgente non è la migliore idea in questo caso. Il codice che è responsabile dell'esecuzione del codice compilato (ceval.c) non sembra essere molto dettagliato per una persona che vede sorgenti Python per la prima volta. Ecco il frammento di che rappresenta l'iterazione in cicli for:

TARGET(FOR_ITER) 
     /* before: [iter]; after: [iter, iter()] *or* [] */ 
     v = TOP(); 

     /* 
      Here tp_iternext corresponds to next() in Python 
     */ 
     x = (*v->ob_type->tp_iternext)(v); 
     if (x != NULL) { 
      PUSH(x); 
      PREDICT(STORE_FAST); 
      PREDICT(UNPACK_SEQUENCE); 
      DISPATCH(); 
     } 
     if (PyErr_Occurred()) { 
      if (!PyErr_ExceptionMatches(
          PyExc_StopIteration)) 
       break; 
      PyErr_Clear(); 
     } 
     /* iterator ended normally */ 
     x = v = POP(); 
     Py_DECREF(v); 
     JUMPBY(oparg); 
     DISPATCH(); 

per trovare ciò che effettivamente accade qui è necessario per tuffarsi nel mucchio di altri file, che prolissità non è molto meglio. Quindi penso che in questi casi la documentazione e i siti come SO siano il primo posto dove andare mentre la fonte dovrebbe essere controllata solo per i dettagli di implementazione scoperti.