Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

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.

Levels: Advanced ~18 min read

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.

python module-fallback.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import sys

def __getattr__(name):
    if name == "region":
        return "eu-west"
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

module = sys.modules[__name__]
print(module.region)
try:
    print(module.unknown)
except AttributeError as error:
    print(type(error).__name__)

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.

python normal-first.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import sys
calls = []
value = 7

def __getattr__(name):
    calls.append(name)
    if name == "computed":
        return 42
    raise AttributeError(name)

module = sys.modules[__name__]
print(module.value)
print(module.computed)
print(calls)

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.

python lazy-import.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import importlib
import sys

def __getattr__(name):
    if name == "sqrt":
        value = importlib.import_module("math").sqrt
        globals()[name] = value
        return value
    raise AttributeError(name)

module = sys.modules[__name__]
print(module.sqrt(81))
print("sqrt" in globals())

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.

python module-dir.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import sys
public_value = "ready"

def __getattr__(name):
    if name == "computed":
        return 12
    raise AttributeError(name)

def __dir__():
    return ["public_value", "computed"]

module = sys.modules[__name__]
print(module.computed)
print(dir(module))

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.

python direct-globals.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import sys

def __getattr__(name):
    if name == "answer":
        return 42
    raise AttributeError(name)

module = sys.modules[__name__]
print(module.answer)
print("answer" in module.__dict__)
print(getattr(module, "answer"))

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.
Was this page helpful?

Mark this learning resource complete to track your learning progress.