Function attributes in Python

Last updated on September 05, 2023, in python

It's well known that in Python, every function is a first-class object, which gives us the ability to pass them as arguments, assign to variables, compare to each other and return from other functions. But what about rarely used custom function attributes?

Function 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.

def func():
    print(func.a)
    func.a -= 10


func.a = 10
func.foo = "bar"
func()
func.a += 2
func()
print(func.__dict__)

Internally, it's just a dictionary that handles failed attribute lookups (i.e., nondefault attributes). You can access or even replace such dictionary using __dict__ attribute. PEP 232 has an extensive description of this feature.

For example, you can track the number of times a function was called:

def func(a, b):
    func.ncalls += 1
    return a + b


func.ncalls = 0

func(1, 2)
func(3, 2)
print(func.ncalls)

Or you can use it to cache results of a function:

def cached_sum(a, b):
    key = (a, b)
    if key not in cached_sum.cache:
        cached_sum.cache[key] = a + b
    return cached_sum.cache[key]

You can find such a technique in popular Python projects. In general, it's better to avoid using this because it makes people confused and requires extra comments to explain the behavior.


If you have any questions, feel free to ask them via e-mail displayed in the footer.

Comments

  • I had no idea 2024-03-03 #

    That this was a consequence of functions being first-class objects. I was looking at the docs for scipy.integrate solve_ivp and they created a simple function with a return, and afterwards, set an attribute. I had no idea this was possible.

    In the scipy docs: def some_function(t, y): return y; some_function.terminal = True

    reply