I was surprised to learn that functions and methods can be assigned attributes, similarly to how one can assign attributes to classes, class instances, and most other objects:
For what reason is this even possible?
You might be thinking:
“Of course this works. Functions are objects. Everything is an object. And therefore it makes sense that functions have assignable attributes. Classes are objects. Classes have assignable attributes. Functions are also objects. Therefore functions must also have assignable attributes.”
Well, just because something is an object doesn’t mean it’s an attribute assignment free-for-all. In fact, Python explicitly forbids attribute assignment in a couple common cases.
For example you can’t assign attributes to built-in functions:
And you can’t assign attributes to built-in types:
And yes, both are objects:
Surprisingly though, Python allows attribute assignment to (non-built-in) functions and methods. And you can put anything you want in there! Check it out:
This has not always been the case. Functions did not initially support attribute assignment. But despite not supporting attribute assignment, functions were never entirely attribute-less. In even the earlier days of Python, functions had a number of attributes, including a few that were writeable—namely
If you’ve ever documented your code, you’re probably familiar with docstrings. Docstrings are syntactic sugar that enable one to write documentation for modules, classes, methods and functions by putting an inline string literal at the top of the object’s definition:
The docstring of a function is stored in its
So what do docstrings have to do with function attributes?
In the earlier days of Python, engineers began hijacking docstrings for untoward purposes—not using the docstring for documentation, but instead for storing other programmatically accessible information about the function.
John Aycock, for example, wrote a clever parsing system that decided whether to apply a method to a target string by evaluating whether that target string matched a regular expression contained in the method’s docstring.
In response to docstring exploitation, Barry Warsaw proposed PEP 232, an extension to the Python language adding support for method/function attributes. It was approved and implemented in Python 2.1+.
When Is It Appropriate To Use Function Attributes?
In my opinion: rarely.
Many experienced Pythonistas aren’t aware that it is even possible to assign function attributes. If you’re doing work-a-day software development, lack of familiarity in the community is usually a red flag for a language feature.
That being said, there are valid reasons one might store metadata on a function.
Function attributes are useful if you are passing functions around and need to a way to graft information onto those functions to use later. Perhaps you’re writing a higher order function—a function that takes a function as an argument. You might want your higher order function to treat the lower order function differently depending on the value stored in an attribute of the function:
I should note that in Python 3.4+, the
functools.singledispatch decorator better serves the above use case. I’ll talk about
singledispatch in a future post. But my point stands: when doing higher-order programming, it can be useful for functions to carry around data about themselves.
For example, one might want to define different docstring formats in order to accommodate different IDEs or documentation methodologies.
In a similar vein as John Aycock’s parser: if you’re writing a class for traversing a data structure, and you want to associate particular node types with certain methods, the list of node types that a method supports can be stored as an attribute of that method.
When Is It Not Appropriate To Use Function Attributes?
If you need a quick-and-dirty data store and believe dot notation is prettier than dict[key]:
Seriously, don’t do this. If the above code landed on my desk for review, I’d give the engineer a sincere pat on the back for being clever, then politely request they be sent to the gulag for their crimes.
If you need a store of data, use a dictionary, class, or even a module. Anything but a function.
Another deeply inappropriate reason for leveraging function attributes is that you are dissatisfied with off-the-shelf class behavior and want to create functions that sort of behave like classes. You can accomplish that with function attributes and a funky bit of recursion:
Please don’t do this either.
I hope you enjoyed this deep-ish dive into Python function attributes. This is one article in a series on Python arcana I’ll be publishing over the course of the next few months. Check back soon for more!