¿Cuáles son los usos de iter (callable, centinela)?

Entonces, estaba viendo la charla de Raymond Hettinger Transformando el código en una hermosa e idiomática Python y él me muestra esta forma de iter que nunca tuve conocimiento. Su ejemplo es el siguiente:

En lugar de:

 blocks = [] while True: block = f.read(32) if block == '': break blocks.append(block) 

Utilizar:

 blocks = [] read_block = partial(f.read, 32) for block in iter(read_block, ''): blocks.append(block) 

Después de revisar la documentación de iter , encontré un ejemplo similar:

 with open('mydata.txt') as fp: for line in iter(fp.readline, ''): process_line(line) 

Esto me parece bastante útil, pero me preguntaba si de ustedes los pitonistas saben de algún ejemplo de este constructo que no involucre bucles de lectura / entrada / salida? Tal vez en la biblioteca estándar?

Puedo pensar en ejemplos muy artificiales, como los siguientes:

 >>> def f(): ... f.count += 1 ... return f.count ... >>> f.count = 0 >>> list(iter(f,20)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] >>> 

Pero obviamente esto no es más útil que los iterables incorporados. Además, me parece que el código huele cuando asignas el estado a una función. En ese momento, probablemente debería estar trabajando con una clase, pero si voy a escribir una clase, también podría implementar el protocolo del iterador para lo que quiera lograr.

Como regla general, los usos principales que he visto para dos argumentos incluyen la conversión de funciones que son similares a las API de C (estado implícito, sin concepto de iteración) a iteradores. Los objetos similares a archivos son un ejemplo común, pero se muestran en otras bibliotecas que contienen mal las API de C. El patrón que esperaría sería uno visto en las API como FindFirstFile / FindNextFile , donde se abre un recurso, y cada llamada avanza al estado interno y devuelve un nuevo valor o una variable de marcador (como NULL en C). Lo mejor es envolverlo en una clase que implemente el protocolo del iterador, pero si tiene que hacerlo usted mismo, mientras que la API tiene un nivel de C incorporado, el ajuste puede terminar ralentizando el uso, donde dos argumentos, implementados en C como Bueno, puede evitar el gasto de la ejecución de código de byte adicional.

Otros ejemplos involucran objetos mutables que se cambian durante el propio bucle, por ejemplo, el bucle en orden inverso sobre las líneas en un bytearray, eliminando la línea solo una vez que se completa el procesamiento:

 >>> from functools import partial >>> ba = bytearray(b'aaaa\n'*5) >>> for i in iter(partial(ba.rfind, b'\n'), -1): ... print(i) ... ba[i:] = b'' ... 24 19 14 9 4 

Otro caso es cuando se usa la segmentación de manera progresiva, por ejemplo, una forma eficiente (aunque ciertamente fea) de agrupar un iterable en grupos de n elementos, mientras que el grupo final es menor que n elementos si la entrada iterable no es una incluso varios de n elementos de longitud (este lo he usado en realidad, aunque normalmente lo uso itertools.takewhile(bool lugar de dos itertools.takewhile(bool ):

 # from future_builtins import map # Python 2 only from itertools import starmap, islice, repeat def grouper(n, iterable): '''Returns a generator yielding n sized tuples from iterable For iterables not evenly divisible by n, the final group will be undersized. ''' # Keep islicing n items and converting to groups until we hit an empty slice return iter(map(tuple, starmap(islice, repeat((iter(iterable), n)))).__next__, ()) # Use .next instead of .__next__ on Py2 

Otro uso: escribir varios objetos decapados en un solo archivo, seguido de un valor de centinela ( None por ejemplo), por lo que al desempaquetar, puede usar este idioma en lugar de tener que recordar de alguna manera el número de elementos decapados o la necesidad de llamar a la load y otra vez hasta que EOFError :

 with open('picklefile', 'rb') as f: for obj in iter(pickle.Unpickler(f).load, None): ... process an object ... 

Aquí hay un ejemplo tonto que se me ocurrió:

 from functools import partial from random import randint pull_trigger = partial(randint, 1, 6) print('Starting a game of Russian Roulette...') print('--------------------------------------') for i in iter(pull_trigger, 6): print('I am still alive, selected', i) print('Oops, game over, I am dead! :(') 

Salida de muestra:

 $ python3 roulette.py Starting a game of Russian Roulette... -------------------------------------- I am still alive, selected 2 I am still alive, selected 4 I am still alive, selected 2 I am still alive, selected 5 Oops, game over, I am dead! :( 

La idea es tener un generador que produzca valores aleatorios, y desea un proceso una vez que se haya seleccionado un valor particular. Podría, por ejemplo, utilizar este patrón en cada ejecución de una simulación que intenta determinar el resultado promedio de un proceso estocástico.

Por supuesto, el proceso que estaría modelando probablemente sería mucho más complicado bajo el capó que una simple tirada de dados …

Otro ejemplo que se me ocurre sería realizar una operación repetidamente hasta que tenga éxito, indicado por un mensaje de error vacío (supongamos que aquí se diseña una función de terceros en lugar de, por ejemplo, usar excepciones):

 from foo_lib import guess_password for msg in iter(guess_password, ''): print('Incorrect attempt, details:', msg) # protection cracked, continue... 

En el código de multiproceso / multiproceso, (con suerte) encontrará este constructo a menudo para sondear una cola o canalización. En la biblioteca estándar también encontrarás esto en multiprocessing.Pool :

 @staticmethod def _handle_tasks(taskqueue, put, outqueue, pool, cache): thread = threading.current_thread() for taskseq, set_length in iter(taskqueue.get, None): task = None try: # iterating taskseq cannot fail for task in taskseq: ... else: util.debug('task handler got sentinel') 

Hace algún tiempo encontré esta entrada de blog, en la que IMO concluye la ventaja de iter(callable, sentinel) while True ... break :

Usualmente, cuando iteramos sobre un objeto o hasta que ocurre una condición, entendemos el scope del bucle en su primera línea. por ejemplo, cuando leemos un ciclo que comienza con libros en libros, nos damos cuenta de que estamos repitiendo sobre todos los libros. Cuando vemos un bucle que comienza con la opción sin batería.empty () nos damos cuenta de que el scope del bucle es mientras tengamos batería. Cuando decimos “Hacer para siempre” (es decir, si bien es cierto), es obvio que este scope es una mentira . Por lo tanto, es necesario que mantengamos ese pensamiento en nuestra cabeza y busquemos en el rest del código una statement que nos permita salir de él. Estamos entrando en el bucle con menos información y por lo tanto es menos legible.