| You are here: Inicio > Inmersión en Python > Funciones dinámicas > plural.py, fase 6 | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
Ahora está listo para que le hable de generadores.
import re def rules(language): for line in file('rules.%s' % language): pattern, search, replace = line.split() yield lambda word: re.search(pattern, word) and re.sub(search, replace, word) def plural(noun, language='en'): for applyRule in rules(language): result = applyRule(noun) if result: return result
Esto usa una técnica llamada generadores que no voy siquiera a intentar explicar hasta que eche un vistazo antes a un ejemplo más simple.
>>> def make_counter(x): ... print 'entering make_counter' ... while 1: ... yield x... print 'incrementando x' ... x = x + 1 ... >>> counter = make_counter(2)
>>> counter
<generator object at 0x001C9C10> >>> counter.next()
entering make_counter 2 >>> counter.next()
incrementando x 3 >>> counter.next()
incrementando x 4
| La presencia de la palabra reservada yield en make_counter significa que esto no es una función normal. Es un tipo especial de función que genera valores uno por vez. Puede pensar en ella como una función de la que se puede salir a la mitad para volver luego al punto donde se dejó. Invocarla devolverá un generador que se puede usar para crear valores sucesivos de x. | |
| Para crear una instancia del generador make_counter basta invocarla como cualquier otra función. Advierta que esto no ejecuta realmente el código de la función. Lo sabemos porque la primera línea de make_counter es una sentencia print, pero aún no se ha mostrado nada. | |
| La función make_counter devuelve un objeto generador. | |
| La primera vez que llamamos al método next() del generador, ejecuta el código de make_counter hasta la sentencia yield y luego devuelve el valor cedido[23]. En este caso será 2, porque originalmente creamos el generador llamando a make_counter(2). | |
| Invocar next() repetidamente sobre el objeto generador lo reanuda donde lo dejó y continúa hasta que encontramos la siguiente sentencia yield. La siguiente línea de código que espera ser ejecutada es la sentencia print que imprime incrementando x, y tras esto la x = x + 1 que la incrementa. Entonces entramos de nuevo en el bucle while y lo primero que hacemos es yield x, que devuelve el valor actual de x (ahora 3). | |
| La segunda vez que llamamos a counter.next(), hacemos lo mismo pero esta vez x es 4. Y así en adelante. Dado que make_counter crea un bucle infinito podríamos seguir esto para siempre (teóricamente), y se mantendría incrementando x y escupiendo valores. Pero en vez de eso, veamos usos más productivos de los generadores. |
def fibonacci(max): a, b = 0, 1while a < max: yield a
a, b = b, a+b
![]()
Ahora tenemos una función que escupe valores sucesivos de Fibonacci. Bien, podíamos haber hecho eso con recursividad pero de esta manera es más fácil de leer. Además, funciona bien con bucles for.
>>> for n in fibonacci(1000):... print n,
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
Bien, volvamos a la función plural y veamos cómo se usa esto.
def rules(language): for line in file('rules.%s' % language):pattern, search, replace = line.split()
yield lambda word: re.search(pattern, word) and re.sub(search, replace, word)
def plural(noun, language='en'): for applyRule in rules(language):
result = applyRule(noun) if result: return result
¿En qué ha mejorado frente a la fase 5? En la fase 5 leíamos el fichero de reglas entero y creábamos una lista de todas las posibles antes siquiera de probar la primera. Ahora con los generadores podemos hacerlo todo perezosamente: abrimos el fichero, leemos la primera regla y creamos la función que la va a probar, pero si eso funciona no leemos el resto del fichero ni creamos otras funciones.
[23] yielded
<< plural.py, fase 5 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
Resumen >> |