2011-03-14 12 views
7

Estoy luchando por comprender cómo funciona el siguiente código. Es de http://docs.python.org/library/itertools.html#itertools.izip_longest, y es el equivalente de python puro del iterador izip_longest. Estoy especialmente desconcertado por la función centinela, ¿cómo funciona?izip_longest en itertools: ¿Qué está pasando aquí?

def izip_longest(*args, **kwds): 
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- 
    fillvalue = kwds.get('fillvalue') 
    def sentinel(counter = ([fillvalue]*(len(args)-1)).pop): 
     yield counter()   # yields the fillvalue, or raises IndexError 
    fillers = repeat(fillvalue) 
    iters = [chain(it, sentinel(), fillers) for it in args] 
    try: 
     for tup in izip(*iters): 
      yield tup 
    except IndexError: 
     pass 

Respuesta

6

Ok, podemos hacer esto. Sobre el centinela. La expresión ([fillvalue]*(len(args)-1)) crea una lista que contiene un valor de relleno para cada iterable en args, menos uno. Por lo tanto, para el ejemplo anterior ['-']. A counter se le asigna la función pop de esa lista. sentinel es un generator que muestra un elemento de esa lista en cada iteración. Puede iterar sobre cada iterador devuelto por sentinel exactamente una vez, y siempre obtendrá fillvalue. El número total de elementos generados por todos los iteradores devueltos por sentinel es len(args) - 1 (gracias a Sven Marnach por aclarar eso, lo entendí mal).

Ahora echa un vistazo a esto:

iters = [chain(it, sentinel(), fillers) for it in args] 

Ese es el truco. iters es una lista que contiene un iterador para cada iterable en args. Cada uno de estos iteradores hace lo siguiente:

  1. Iterar todos los elementos en el iterable correspondiente desde args.
  2. Iterar el centinela una vez, obteniendo fillvalue.
  3. Repita fillvalue para toda la eternidad.

Ahora, como se estableció anteriormente, sólo podemos iterar sobre todos los centinelas junto len(args)-1 veces antes de que lanza una IndexError. Esto está bien, porque uno de los iterables es el más largo. Entonces, cuando llegamos al punto en que se plantea el IndexError, eso significa que hemos terminado de iterar sobre el iterable más largo en args.

De nada.

P.S .: Espero que esto sea comprensible.

+2

Además, 'sentinel()' se puede llamar un número infinito de veces sin ninguna excepción planteada. –

+0

@Sven: Tienes razón, lo arreglé. –

2

La definición de sentinel es casi equivalente a

def sentinel(): 
    yield ([fillvalue] * (len(args) - 1)).pop() 

excepto que se pone el método vinculado pop (un objeto función) como un argumento predeterminado. Un argumento predeterminado se evalúa en el momento de la definición de la función, por lo que una vez por llamada a izip_longest en lugar de una vez por llamada al sentinel. Por lo tanto, el objeto de función "recuerda" la lista [fillvalue] * (len(args) - 1) en lugar de construir esto de nuevo en cada llamada.

5

La función sentinel() devuelve iteradores que producen fillvalue exactamente una vez. El número total de fillvalue s producido por todos los iteradores devueltos por sentinel() está limitado a n-1, donde n es el número de iteradores pasados ​​a izip_longest(). Después de que se haya agotado este número de fillvalue s, una iteración adicional sobre un iterador devuelto por sentinel() aumentará un IndexError.

Esta función se usa para detectar si se han agotado todos los iteradores: cada iterador es chain() ed con un iterador devuelto por sentinel(). Si todos los iteradores están agotados, un iterador devuelto por sentinel() se repetirá durante el n vez, dando como resultado un IndexError, activando el final de izip_longest() sucesivamente.

Hasta ahora he explicado qué hace sentinel(), no cómo funciona. Cuando se llama a izip_longest(), se evalúa la definición de sentinel(). Al evaluar la definición, también se evalúa el argumento predeterminado sentinel(), una vez por llamada al izip_longest(). El código es equivalente a

fillvalue_list = [fillvalue] * (len(args)-1) 
def sentinel(): 
    yield fillvalue_list.pop() 

Almacenamiento de esto en un argumento por defecto en lugar de en una variable en un ámbito de inclusión es sólo una optimización, como es la inclusión de .pop en el argumento por defecto, ya que guardar mirar hacia arriba cada vez que se itera un iterador devuelto por sentinel().

Cuestiones relacionadas