A client that builds URLs from attribute names
This is the pattern behind many API clients: every public name the lookup cannot find becomes a deeper path. Special-method calls such as len(api) skip __getattr__ completely.
Reading obj.name follows a fixed search: data descriptors on the type, then the instance's __dict__, then the class and its parents in MRO order, and only then __getattr__. Methods are plain functions that become bound methods when you read them.
Change the code and press Run (Ctrl+Enter). Predict what will happen first, then break it on purpose and read what changes. Your edits are saved and match the lesson page.
Ready for more? This phase has 6 hands-on exercises with automatic checks.
Practice questions
Write the code in the editor, run it, then open the model answer to compare.
Write a class Lenient with a class attribute shade = "blue" and a __getattr__ that returns "?" for any missing attribute. Print o.shade and o.colour. Then set o.shade = "red", print o.shade and Lenient.shade, delete o.shade and print it again.
Write a class Frozen that accepts x in __init__ but refuses every later assignment by overriding __setattr__. Show that f.x = 4 raises AttributeError.
Using Config from this topic, show that cfg.host works and cfg.timeout raises AttributeError.
Explain it without notes
Describe the order in which Python looks up obj.name.
What is the difference between __getattr__ and __getattribute__?
Why is obj.method is obj.method False?
What happens when you assign obj.x = 5 and the class already has x = 1?
Expected output
GET https://api.example.test/users/list
GET https://api.example.test/users/list?active=true&limit=10
GET https://api.example.test/invoices/paid/summary?year=2025
True
False
len() looks on the class, not through __getattr__: TypeError