| You are here: Inicio > Inmersión en Python > Funciones dinámicas > plural.py, fase 4 | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
Eliminemos la duplicación del código para que sea más sencillo definir nuevas reglas.
import re def buildMatchAndApplyFunctions((pattern, search, replace)): matchFunction = lambda word: re.search(pattern, word)applyFunction = lambda word: re.sub(search, replace, word)
return (matchFunction, applyFunction)
![]()
| buildMatchAndApplyFunctions es una función que construye otras de forma dinámica. Toma pattern, search y replace (en realidad toma una tupla, pero hablaremos sobre eso en un momento), y podemos construir la función de comparación usando la sintaxis lambda para que sea una función que toma un parámetro (word) e invoca a re.search con el pattern que se pasó a la función buildMatchAndApplyFunctions, y la word que se pasó a la función de comparación que estamos construyendo. ¡Guau! | |
| La construcción la función de transformación se realiza igual. La función de transformación toma un parámetros y llama a re.sub con los parámetros search y replace que se pasaron a la función buildMatchAndApplyFunctions, y la word que se pasó a la función de transformación que estamos creando. Este técnica de usar los valores de parámetros externos dentro de una función dinámica se denomina closure[21]. Esencialmente estamos definiendo constantes dentro de la función de transformación que estamos creando: toma un parámetro (word), pero luego actúa sobre otros dos valores más (search y replace) cuyos valores se fijaron cuando definimos la función de transformación. | |
| Por último, la función buildMatchAndApplyFunctions devuelve una tupla con los dos valores: las dos funciones que acabamos de crear. Las constantes que definimos dentro de esas funciones (pattern dentro de matchFunction, y search y replace dentro de applyFunction) se quedan en esas funciones incluso tras volver de buildMatchAndApplyFunctions. ¡Esto es de locura! |
Si esto es increíblemente confuso (y debería serlo, es materia muy absurda), puede que quede más claro cuando vea cómo usarlo.
patterns = \
(
('[sxz]$', '$', 'es'),
('[^aeioudgkprt]h$', '$', 'es'),
('(qu|[^aeiou])y$', 'y$', 'ies'),
('$', '$', 's')
)
rules = map(buildMatchAndApplyFunctions, patterns)
Le juro que no me estoy inventando esto: rules acaba siendo exactamente la misma lista de funciones del ejemplo anterior. Desenrolle la definición de rules y obtendrá esto:
rules = \
(
(
lambda word: re.search('[sxz]$', word),
lambda word: re.sub('$', 'es', word)
),
(
lambda word: re.search('[^aeioudgkprt]h$', word),
lambda word: re.sub('$', 'es', word)
),
(
lambda word: re.search('[^aeiou]y$', word),
lambda word: re.sub('y$', 'ies', word)
),
(
lambda word: re.search('$', word),
lambda word: re.sub('$', 's', word)
)
)
def plural(noun): for matchesRule, applyRule in rules:if matchesRule(noun): return applyRule(noun)
| Ya que la lista rules es la misma del ejemplo anterior, no debería sorprenderle que no haya cambiado la función plural. Recuerde: es completamente genérica; toma una lista de funciones de reglas y las llama por orden. No le importa cómo se hayan definido. En la fase 2 se definieron como funciones con nombres diferentes. En la fase 3 se definieron como funciones lambda anónimas. Ahora en la fase 4 las estamos construyendo de forma dinámica relacionando la función buildMatchAndApplyFunctions sobre una lista de cadenas sin procesar. No importa; la función plural sigue funcionando de la misma manera. |
Sólo por si acaso su mente no está ya suficientemente machacada, debo confesarle que había una sutileza en la definición de buildMatchAndApplyFunctions que no he explicado. Volvamos atrás a echarle otro vistazo.
def buildMatchAndApplyFunctions((pattern, search, replace)):![]()
>>> def foo((a, b, c)): ... print c ... print b ... print a >>> parameters = ('apple', 'bear', 'catnap') >>> foo(parameters)catnap bear apple
| La manera correcta de invocar las función foo es con una tupla de tres elementos. Cuando se llama a la función se asignan los elementos a diferentes variables locales dentro de foo. |
Ahora vamos a ver por qué era necesario este truco de autoexpansión de tupla. patterns es una lista de tuplas y cada tupla tiene tres elementos. Cuando se invoca map(buildMatchAndApplyFunctions, patterns), eso significa que no estamos llamando a buildMatchAndApplyFunctions con tres parámetros. Si usamos map para relacionar una única lista a una función siempre invocamos la función con un único parámetros: cada elemento de la lista. En el caso de patterns, cada elemento de la lista es una tupla, así que a buildMatchAndApplyFunctions siempre se le llama con la tupla, y usamos el truco de autoexpansión de tuplas en la definición de buildMatchAndApplyFunctions para asignar los elementos de la tupla a variables con las que podemos trabajar.
[21] N. del T.: o cierre
<< plural.py, fase 3 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
plural.py, fase 5 >> |