Una de las partes que más caracterizan a python es lo limpio que puede llegar a ser escribir código, sin embargo fuera de la sintaxis misma existen diferentes propiedades que nos ayudan aún más a reutilizar código previamente escrito. Aquí es cuando entra el tema de la metaprogramación.
Wikipedia define a la Metaprogramación de la siguiente forma:
"La metaprogramación consiste en escribir programas que escriben o manipulan otros programas (o a sí mismos) como datos, o que hacen en tiempo de compilación parte del trabajo que, de otra forma, se haría en tiempo de ejecución. Esto permite al programador ahorrar tiempo en la producción de código."
Básicamente se refiere a código que construye más código con ayuda de el envío de funciones por parámetro a otras funciones.
Si tienes un poco de nociones de JS podrá resultarte un poco parecido al concepto de callback, sin embargo ten en cuenta que no es lo mismo, pues aquí se crean funciones nuevas.
La forma más fácil de empezar a comprender el tema es con el uso de decoradores. Ésta característica del lenguaje permite construir funciones que reutilizan el código de otras funciones de una forma más limpia.
<h3>Ejemplo:</h3>En el siguiente código tenemos dos funciones (response1, response2) que reutilizan una tercer función (save_to_log).
defsave_to_log(log_info):
print("Open Log File")
print("Write {}".format(log_info))
print("Close File")
defresponse1():
response1 = process1()
save_to_log(response1)
defresponse2():
response2 = process2()
save_to_log(response2)
response1()
response2()
Para poder lograr un comportamiento similar con el uso de decoradores, debemos tener en cuenta el uso del caracter “@” el cuál indica que una función será usada bajo éste patrón y que debe recibir como parámetro una función.
Para evitar la confusión dejo un ejemplo de cómo se pasan las funciones por parámetro en éste patrón.
@decoradordeffuntcion1():pass# resulta en lo mismo que deffunction1():pass
function1 = decorador(function1)
Otra característica de los decoradores es que en realidad componen y retornan una nueva función que será que se ejecutará al final.
defsave_to_log(fn):defwrapper()
log_info = fn()
print("Open Log File")
print("Write {}".format(log_info))
print("Close File")
return wrapper
@save_to_log
defresponse1():return process1()
@save_to_log
defresponse2():return process2()
response1()
response2()
Podemos describir que en el comportamiento anterior la función save_to_log regresa una nueva función que está definida en wrapper y que se ayuda de la función inicialmente pasada por parámetro para su ejecución.
Si dueles escribir las llamadas doc string en tus funciones de python, te ayudas del nombre de la función cuando haces debug ó usas annotations, existe una forma de poder conservarlas aún usando ése patrón.
Para tal caso deberas importar un decorador llamado wraps y usarlo de la siguiente forma.
from functools import wraps
defsave_to_log(fn):
@wraps(fn)
defwrapper()
log_info = fn()
print("Open Log File")
print("Write {}".format(log_info))
print("Close File")
return wrapper
@save_to_log
defresponse1():'''
Calls to process 1
'''return process1()
@save_to_log
defresponse2():'''
Calls to process 2
'''return process2()
response1()
response2()
Así cuando se imprima response1._ _ name _ _ recibiremos el nombre correcto de la función: “response”