tutorial - La mejor manera de agregar atributos a una función de Python
django tutorial (2)
Un mejor enfoque para lograr esto sería con el uso de decoradores, para esto tiene dos opciones:
Decorador basado en funciones:
Puede crear un decorador basado en funciones que acepte como argumento la representación de látex y la adjunte a la función que decora:
def latex_repr(r):
def wrapper(f):
f.latex = r
return f
return wrapper
Luego puede usarlo para definir su función y proporcionar la representación adecuada:
@latex_repr(r''$ax^2 + bx + c$'')
def func(x, a, b, c):
return a*x**2 + b*x + c
Esto se traduce en:
func = latex_repr(r''$ax^2 + bx + c$'')(func)
y hace que el atributo de latex
esté disponible inmediatamente después de definir la función:
print(func.latex)
''$ax^2 + bx + c$''
He hecho que la representación sea un argumento obligatorio, puede definir un valor predeterminado razonable si no desea forzar la representación para que siempre se otorgue.
Decorador basado en la clase:
Si las clases son una preferencia tuya, un decorador basado en clases también se puede usar para un efecto similar de una manera más pitónica que tu intento original:
class LatexRepr:
def __init__(self, r):
self.latex = r
def __call__(self, f):
f.latex = self.latex
return f
lo usas de la misma manera:
@LatexRepr(r''$ax^2 + bx + c$'')
def func(x, a, b, c):
return a*x**2 + b*x + c
print(func.latex)
''$ax^2 + bx + c$''
Aquí, LatexRepr(r''$ax^2 + bx + c$'')
inicializa la clase y devuelve la instancia invocable ( __call__
definida). Lo que esto hace es:
func = LatexRepr(r''$ax^2 + bx + c$'')(func)
# __init__
# __call__
Y hace lo mismo wrapped
hace.
Dado que ambos simplemente agregan un argumento a la función, simplemente lo devuelven como está. No lo sustituyen por otro que se pueda llamar.
Aunque un enfoque basado en clase resuelve el problema, el decorador basado en funciones debería ser más rápido y más liviano.
Usted también preguntó:
"porque Python es" perezoso "al ejecutar funciones": Python simplemente compila el cuerpo de la función, no ejecuta ninguna declaración dentro de él; lo único que ejecuta es el valor predeterminado de los argumentos (vea la famosa Q here ). Es por eso que primero necesita invocar la función para que "obtenga" el atributo de latex
.
El inconveniente adicional de este enfoque es que ejecuta esa asignación cada vez que invoca la función
Tomemos el caso simple de una función de Python que evalúa una función matemática:
def func(x, a, b, c):
"""Return the value of the quadratic function, ax^2 + bx + c."""
return a*x**2 + b*x + c
Supongamos que quiero "adjuntar" información adicional en forma de un atributo de función. Por ejemplo, la representación de LaTeX. Sé que gracias a PEP232 puedo hacer esto fuera de la definición de la función:
def func(x, a, b, c):
return a*x**2 + b*x + c
func.latex = r''$ax^2 + bx + c$''
pero me gustaría hacerlo dentro de la propia definición de la función. Si escribo
def func(x, a, b, c):
func.latex = r''$ax^2 + bx + c$''
return a*x**2 + b*x + c
esto ciertamente funciona, pero solo después de que haya llamado a la func
por primera vez (porque Python es "perezoso" al ejecutar funciones (?))
¿Es mi única opción escribir una clase llamable?
class MyFunction:
def __init__(self, func, latex):
self.func = func
self.latex = latex
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
func = MyFunction(lambda x,a,b,c: a*x**2+b*x+c, r''$ax^2 + bx + c$'')
¿O hay una característica del lenguaje que estoy pasando por alto para hacer esto cuidadosamente?
Ya que trata sus funciones como entidades más complejas que las funciones simples de Python, ciertamente tiene mucho sentido representarlas como instancias exigibles de una clase designada definida por el usuario, como sugirió.
Sin embargo, una forma más simple y común de hacer lo que quiere es usar decoradores:
def with_func_attrs(**attrs):
def with_attrs(f):
for k,v in attrs.items():
setattr(f, k, v)
return f
return with_attrs
@with_func_attrs(latex = r''$ax^2 + bx + c$'', foo = ''bar'')
def func(...):
return ...