2015-05-12 21 views
16

Quando si esegue il wrapping di un iteratore (interno), è spesso necessario reindirizzare il metodo __iter__ al sottostante iterabile. Considera il seguente esempio:"yield from iterable" vs "return iter (iterable)"

class FancyNewClass(collections.Iterable): 
    def __init__(self): 
     self._internal_iterable = [1,2,3,4,5] 

    # ... 

    # variant A 
    def __iter__(self): 
     return iter(self._internal_iterable) 

    # variant B 
    def __iter__(self): 
     yield from self._internal_iterable 

C'è qualche differenza significativa tra la variante A e B? Variant A restituisce un oggetto iteratore che è stato interrogato tramite iter() dall'interno iterabile. Variant B restituisce un oggetto generatore che restituisce valori dall'interno iterabile. L'uno o l'altro è preferibile per qualche motivo? In collections.abc viene utilizzata la versione yield from. La variante return iter() è lo schema che ho usato fino ad ora.

risposta

11

L'unica differenza significativa è ciò che accade quando viene sollevata un'eccezione dall'interno iterabile. Usando return iter() il tuo FancyNewClass non apparirà sul traceback di eccezione, mentre con yield from lo farà. Generalmente è una buona cosa avere quante più informazioni possibili sul traceback, anche se potrebbero esserci situazioni in cui si desidera nascondere il proprio wrapper.

Altre differenze:

  • return iter deve caricare il nome iter da globali - questo è potenzialmente lento (anche se improbabile che possa influenzare in maniera significativa le prestazioni) e potrebbero essere pasticciato con (anche se qualcuno che sovrascrive globali del genere merita cosa ottengono).

  • Con yield from è possibile inserire altre espressioni yield prima e dopo (anche se è possibile utilizzare ugualmente itertools.chain).

  • Come presentato, sotto forma yield from scarta qualsiasi valore di ritorno del generatore (cioè raise StopException(value). È possibile risolvere questo scrivendo invece return (yield from iterator).

Ecco un test di confronto tra lo smontaggio dei due approcci e mostrando anche eccezioni traceback: http://ideone.com/1YVcSe

Uso return iter():

3   0 LOAD_GLOBAL    0 (iter) 
       3 LOAD_FAST    0 (it) 
       6 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
       9 RETURN_VALUE 
Traceback (most recent call last): 
    File "./prog.py", line 12, in test 
    File "./prog.py", line 10, in i 
RuntimeError 

Utilizzo return (yield from):

5   0 LOAD_FAST    0 (it) 
       3 GET_ITER 
       4 LOAD_CONST    0 (None) 
       7 YIELD_FROM 
       8 RETURN_VALUE 
Traceback (most recent call last): 
    File "./prog.py", line 12, in test 
    File "./prog.py", line 5, in bar 
    File "./prog.py", line 10, in i 
RuntimeError 
+0

Supponendo l'obiettivo è quello di trasferire semplicemente/esporre l'iterabile sottostante, ovvero nessuna modifica/iniezione di altri elementi, penso di restare con 'iter()' perché: 1) se non aggiungere alcun comportamento aggiuntivo, non ne ho bisogno per apparire sul traceback di eccezione (credo?). 2) per evitare di usare il globale 'iter()' potrei chiamare 'return internal_iterable .__ iter __()'. Riguardo al terzo punto che hai sollevato, dovrò pensare a cosa significhi. – PeterE