Take the simple case of a Python function which evaluates a mathematical function:
def func(x, a, b, c):
"""Return the value of the quadratic function, ax^2 + bx + c."""
return a*x**2 + b*x + c
Suppose I want to "attach" some further information in the form of a function attribute. For example, the LaTeX representation. I know that thanks to PEP232 I can do this outside the function definition:
def func(x, a, b, c):
return a*x**2 + b*x + c
func.latex = r'$ax^2 + bx + c$'
but I'd like to do it within the function definition itself. If I write
def func(x, a, b, c):
func.latex = r'$ax^2 + bx + c$'
return a*x**2 + b*x + c
this certainly works, but only after I've called the func
for the first time (because Python is "lazy" in executing functions(?))
Is my only option to write a callable class?
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$')
Or is there a feature of the language that I'm overlooking to do this neatly?
A better approach to accomplish this would be with the use of decorators, for this you have two options:
You can create a function-based decorator that accepts as an argument the latex representation and attaches it to the function it decorates:
def latex_repr(r):
def wrapper(f):
f.latex = r
return f
return wrapper
Then you can use it when defining your function and supply the appropriate representation:
@latex_repr(r'$ax^2 + bx + c$')
def func(x, a, b, c):
return a*x**2 + b*x + c
This translates to:
func = latex_repr(r'$ax^2 + bx + c$')(func)
and makes the latex
attribute available immediately after defining the function:
print(func.latex)
'$ax^2 + bx + c$'
I've made the representation be a required argument, you could define a sensible default if you don't want to force the representation to always be given.
If classes are a preference of yours, a class-based decorator can also be used for a similar effect in a more Pythonic way than your original attempt:
class LatexRepr:
def __init__(self, r):
self.latex = r
def __call__(self, f):
f.latex = self.latex
return f
you use it in the same way:
@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$'
Here LatexRepr(r'$ax^2 + bx + c$')
initializes the class and returns the callable instance (__call__
defined). What this does is:
func = LatexRepr(r'$ax^2 + bx + c$')(func)
# __init__
# __call__
and does the same thing wrapped
does.
Since they both just add an argument to the function, they just return it as-is. They don't replace it with another callable.
Although a class-based approach does the trick, the function-based decorator should be faster and more lightweight.
You additionally asked:
"because Python is "lazy" in executing functions": Python just compiles the function body, it doesn't execute any statements inside it; the only thing it does execute is default argument values (See famous Q here). That's why you first need to invoke the function for it to 'obtain' the attribute latex
.
The additional downside to this approach is that you execute that assignment everytime you invoke the function