¿Dónde usar el rendimiento en Python mejor?

Sé cómo funciona el yield . Conozco la permutación, piénsalo como una simplicidad matemática.

¿Pero cuál es la verdadera fuerza del yield ? ¿Cuándo debo usarlo? Un ejemplo simple y bueno es mejor.

yield se utiliza mejor cuando tiene una función que devuelve una secuencia y desea iterar sobre esa secuencia, pero no necesita tener todos los valores en la memoria a la vez.

Por ejemplo, tengo una secuencia de comandos de Python que analiza una gran lista de archivos CSV y quiero devolver cada línea para que sea procesada en otra función. No quiero almacenar todos los megabytes de datos en la memoria de una vez, así que yield cada línea en una estructura de datos de python. Por lo tanto, la función para obtener líneas del archivo puede verse como:

 def get_lines(files): for f in files: for line in f: #preprocess line yield line 

Luego puedo usar la misma syntax que con las listas para acceder a la salida de esta función:

 for line in get_lines(files): #process line 

Pero me ahorro mucho uso de memoria.

En pocas palabras, el yield le da un generador. Lo usaría donde normalmente usaría un return en una función. Como un ejemplo realmente artificial cortado y pegado desde un aviso …

 >>> def get_odd_numbers(i): ... return range(1, i, 2) ... >>> def yield_odd_numbers(i): ... for x in range(1, i, 2): ... yield x ... >>> foo = get_odd_numbers(10) >>> bar = yield_odd_numbers(10) >>> foo [1, 3, 5, 7, 9] >>> bar  >>> bar.next() 1 >>> bar.next() 3 >>> bar.next() 5 

Como puede ver, en el primer caso, foo guarda la lista completa en la memoria a la vez. No es un gran problema para una lista con 5 elementos, pero ¿qué sucede si desea una lista de 5 millones? No solo es un gran devorador de memoria, sino que también cuesta mucho tiempo de comstackción en el momento en que se llama a la función. En el segundo caso, la bar solo te da un generador. Un generador es un iterable, lo que significa que puede usarlo en un bucle for, etc., pero solo se puede acceder a cada valor una vez. Todos los valores tampoco se almacenan en la memoria al mismo tiempo; el objeto generador “recuerda” dónde estaba en el bucle la última vez que lo llamó, de esta manera, si está utilizando un iterable para (digamos) contar hasta 50 mil millones, no tiene que contar hasta 50 mil millones de todos a la vez y almacenar los 50 mil millones de números para contar hasta. Nuevamente, este es un ejemplo bastante ingenioso, probablemente usaría itertools si realmente quisiera contar hasta 50 mil millones. 🙂

Este es el caso de uso más simple de los generadores. Como dijiste, se puede usar para escribir permutaciones eficientes, usando el yield para impulsar las cosas a través de la stack de llamadas en lugar de usar algún tipo de variable de stack. Los generadores también se pueden usar para el recorrido de árboles especializados, y todo tipo de cosas.

Otras lecturas:

Otro uso es en un cliente de red. Utilice el ‘rendimiento’ en una función de generador para redondear a través de múltiples sockets sin la complejidad de los hilos.

Por ejemplo, tenía un cliente de prueba de hardware que necesitaba enviar los planos R, G, B de una imagen al firmware. Los datos debían enviarse a la perfección: rojo, verde, azul, rojo, verde, azul. En lugar de generar tres subprocesos, tuve un generador que leyó del archivo, codificó el búfer. Cada tampón era un ‘rendimiento buf’. Fin de archivo, función devuelta y tuve fin de iteración.

El código de mi cliente repasó las tres funciones del generador, obteniendo buffers hasta el final de la iteración.

Estoy leyendo estructuras de datos y algoritmos en Python

Hay una función de fabonacci que usa el rendimiento. Creo que es el mejor momento para usar el rendimiento.

 def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a+b 

Puedes usar esto como:

 f = fibonacci() for i, f in enumerate(f): print i, f if i >= 100: break 

Entonces, creo que, cuando el siguiente elemento depende de elementos anteriores, es hora de usar el rendimiento.