Photo by Noelle Otto

Attribute Your Success to Python

A Python Brain Teaser

Miki Tebeka
2 min readAug 10, 2022

--

https://pragprog.com/newsletter/

What do you think the following program will print?

This program will fail with: RecursionError: maximum recursion depth exceeded.

The Python attribute lookup, which happens every time you write now.year for example, is pretty complicated. As a developer, you have many options to change the way attribute lookup works in your classes: properties, mangled names (for example, __name), staticmethod, classmethod, __slots__, descriptors and more.

You can also implement two special methods to dynamically calculate attributes, these are __getattribute__and __getattr__. You’ll want to use __getattr__ :)

Let’s look at the documentation:

  • __getattr__: Called when the default attribute access fails with an AttributeError …
  • __getattribute__ : Called unconditionally to implement attribute accesses for instances of the class. If the class also defines __getattr__, the latter will not be called …

So, according to the documentation, __getattr__is called only when the normal attribute lookup fails. In our case, writing new._objwill not print anything, but writing now.year will trigger our __getattr__ method (since now does not have a year attribute) and will print.

On the other hand, __getattribute__ bypasses the normal attribute lookup entirely. The line val = getattr(self._obj, attr, None) will call our __getattribute__again and again until we reach Python’s built in recursion limit.

The fix is easy — switch to __getattr__:

Which will print:

[DEBUG] year -> 2022
2022

I can’t remember a single time in my twenty-five years of writing Python code that I used __getattribute__. I did use __getattr__ several times, for example — when writing proxies.)

🎓 Further study. I recommend watching Raymond Hettinger’s Class Development Toolkit video. It’ll give you a good insight on when to use some of the tools mentioned. Or, if you want to go down the C code rabbit hole, start with PyObject_GetAttr in object.c and follow the code.

--

--