Python Module Attribute Access
Learn how module-level __getattr__ and __dir__ customize missing attribute lookup and module introspection without confusing attribute access with global-name resolution.
Advanced
Python modules normally expose names stored in their module namespace. The data model also allows a module to customize missing attribute lookup and the names reported by
dir(). This lesson is about those module-level hooks, not the instance-level attribute hooks taught in Custom Attribute Access.
Use module __getattr__ as a fallback
A module can define a function named
__getattr__ at its top level. When normal module attribute lookup cannot find a name, Python calls that function with the missing attribute name. Return a computed value, or raise
AttributeError if the name is genuinely unsupported. In real modules, the function is defined directly in the module source.
|
|
The module provides region only when normal lookup fails, and reports an unknown name with AttributeError.
Quick check
Which function provides a fallback for a missing module attribute?
import sys
def __getattr__(name):
if name == "mode": return "safe"
raise AttributeError(name)
print(sys.modules[__name__].mode)
Normal attributes are found first
Module
__getattr__ is a fallback, not an interceptor for every attribute. Existing module attributes are returned through normal lookup, so the hook runs only for names that are missing. This differs from instance
__getattribute__, which participates in every ordinary instance attribute read.
|
|
The hook is not called for value because the module already defines it; computed uses the fallback.
Quick check
When is a module-level __getattr__ normally consulted?
import sys
calls = []
ready = True
def __getattr__(name):
calls.append(name)
return 9
module = sys.modules[__name__]
print(module.ready, calls)
print(module.other, calls)
Load an attribute lazily and cache it
One practical use is to defer an import until a public module attribute is first requested. If the cost is worth it, save the resolved value in
globals() so later lookups use the regular module dictionary. Lazy loading should be intentional: it can make import timing and error timing less obvious.
|
|
The first access imports math.sqrt and stores it in the module globals; the second access can use the cached name.
Customize dir() with __dir__
A module-level
__dir__ function takes no arguments and returns an iterable of names that should be exposed by
dir(module). Python sorts the returned names. This can make dynamically provided public attributes discoverable, but the returned list should describe a useful interface rather than every internal detail.
|
|
The virtual computed attribute is available and appears in the sorted names returned by dir().
Quick check
What should a module-level __dir__ return?
import sys
def __dir__():
return ["virtual", "public"]
print(dir(sys.modules[__name__]))
Module attribute access is not global-name lookup
A module-level
__getattr__ affects attribute-based access such as
module.answer and
getattr(module, "answer"). It does not create a real entry in the module dictionary or change direct global-name resolution inside the module. Check
globals() or
module.__dict__ when you need to know what is actually stored.
|
|
The fallback can answer attribute lookup, but answer is not inserted into the module dictionary unless code explicitly stores it there.
Quick check
Does module __getattr__ add its result to module.__dict__ automatically?
import sys
def __getattr__(name):
if name == "answer": return 42
raise AttributeError(name)
module = sys.modules[__name__]
print(module.answer, "answer" in module.__dict__)
Handle compatibility names carefully
A module can use
__getattr__ to preserve an old public name while warning callers to use a replacement, or to defer loading an expensive dependency. These techniques should remain narrow and documented: return only supported names, raise
AttributeError for unknown names, and avoid concealing genuine programming mistakes.
Quick check
Which exception should a module hook raise for an unsupported name?
import sys
def __getattr__(name):
raise AttributeError(name)
print(hasattr(sys.modules[__name__], "not_available"))
Key takeaways
- Module-level __getattr__(name) is consulted only after normal module attribute lookup fails.
- Module-level __dir__() returns an iterable of names that dir() presents in sorted order.
- Fallback attribute values are not automatically added to the module dictionary.
- Direct global-name lookup and direct access to the module dictionary are distinct from module attribute fallback.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.