Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to add attributes to a Python function

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?

like image 697
xnx Avatar asked Nov 01 '17 13:11

xnx


People also ask

How do you add attributes in Python?

Adding attributes to a Python class is very straight forward, you just use the '. ' operator after an instance of the class with whatever arbitrary name you want the attribute to be called, followed by its value.

How do you add attributes to a function?

The setAttribute() method is used to set or add an attribute to a particular element and provides a value to it. If the attribute already exists, it only set or changes the value of the attribute. So, we can also use the setAttribute() method to update the existing attribute's value.

Can Python function have attributes?

In python, functions too are objects. So they have attributes like other objects. All functions have a built-in attribute __doc__, which returns the doc string defined in the function source code. We can also assign new attributes to them, as well as retrieve the values of those attributes.

Can a function have attributes?

Every function has a number of additional attributes which can be accessed using dot syntax (e.g. func. __name__ ). The dir built-in function returns a list of available attributes of a specified object. Since Python 2.1, functions can have arbitrary attributes, that is, you can use a function as key-value storage.


2 Answers

A better approach to accomplish this would be with the use of decorators, for this you have two options:

Function-based Decorator:

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.

Class-based Decorator:

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

like image 76
Dimitris Fasarakis Hilliard Avatar answered Oct 08 '22 06:10

Dimitris Fasarakis Hilliard


Since you treat your functions as more complex entities than plain Python functions, it certainly makes a lot of sense to represent them as callable instances of a designated user-defined class, like you suggested.

However, a simpler and common way to do what you want is using decorators:

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 ...
like image 27
shx2 Avatar answered Oct 08 '22 07:10

shx2