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:

def do_thing(): 
  return
do_thing.whatever = "hi"

print(do_thing.whatever)
#> hi

Wat?

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:

print.some_data = "foo"
#> AttributeError: 'builtin_function_or_method' object has no attribute 'some_data'

And you can’t assign attributes to built-in types:

var = "stringy string"
var.some_data = "foo"
#> AttributeError: 'str' object has no attribute 'some_data'

And yes, both are objects:

isinstance(print, object)
#> True
isinstance(var, object)
#> True

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:

def do_thing():
  pass
do_thing.sneaky_func = eval
do_thing.sneaky_func("print('O_o')")
#> O_o

Why

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

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:

def do_thing():
  """Here's some documentation"""
  return
help(do_thing)
#> Help on function do_thing in module __main__:
#> do_thing()
#>    Here's some documentation

The docstring of a function is stored in its __doc__ attribute:

def do_thing():
  """Here's some documentation"""
  return
print(do_thing.__doc__)
#> Here's some documentation

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:

def dog_feeder(food_type):
  print(f'feeding {food_type} to the dog')
dog_feeder.species = 'dog'

def cat_feeder(food_type):
  print(f'feeding {food_type} to the cat')
cat_feeder.species = 'cat'

def feed_pet(feed_func):
  # Higher order function that takes a feed function as argument
  if feed_func.species == 'dog':
    feed_func('dog food')
  elif feed_func.species == 'cat':
    pass

feed_pet(dog_feeder)
#> feeding dog food to the dog

feed_pet(cat_feeder)
#> 

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.

PEP 232 and Paul Prescod’s message to the python-dev mailing list describe a few other use cases.

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]:

def settings():
  return

settings.host = '0.0.0.0'
settings.port = 8080

app.run(host=settings.host, port=settings.port)

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:

def Dog():
  def bark():
    print("bark bark")
  Dog.bark = bark
  return Dog

fido = Dog()
fido.bark()
#> bark bark

Please don’t do this either.

Conclusion

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!