Customizing
__dir__ in Dynamic Python Objects
Customizing the __dir__ method in Python allows
developers to define the exact list of attribute names returned by the
built-in dir() function for dynamically constructed
objects. In dynamic programming patterns—such as proxy objects, API
wrappers, and schema-driven data containers—attributes are often
resolved at runtime via __getattr__, making them invisible
to Python's default introspection tools. Implementing a custom
__dir__ method bridges this gap, enabling interactive
debuggers, modern IDEs, and read-eval-print loops (REPLs) to accurately
discover, inspect, and autocomplete dynamically generated
attributes.
The Limitation of Default Introspection
By default, Python determines an object's available attributes by
inspecting its instance dictionary (__dict__), its class
dictionary, and any inherited attributes across its base classes. When
using dynamic dispatch mechanisms like __getattr__,
attributes do not physically exist in these namespaces until they are
requested.
As a result, calling dir() on an instance of a class
that dynamically resolves properties returns only the static, explicitly
declared methods and standard dunder attributes. The dynamic attributes
remain hidden from runtime inspection, impairing code
discoverability.
Enabling IDE Autocompletion and Developer Usability
The primary function of __dir__ customization is
enhancing the developer experience. Modern developer tooling—including
VS Code, PyCharm, IPython, and Jupyter Notebooks—relies directly on the
dir() function to populate tab-completion lists.
Without an overridden __dir__, users of a dynamic class
must consult external documentation or inspect source code to know which
attributes are accessible. By customizing __dir__, a
library author can dynamically assemble and expose the names of
attributes derived from external sources, such as JSON schemas, database
columns, or remote API endpoints.
Implementation Example
To implement a custom __dir__, define a method named
__dir__ on the class that returns an iterable of strings.
The standard approach merges the object's default attributes with the
dynamically resolved keys.
class DynamicRecord:
def __init__(self, data: dict):
self._data = data
def __getattr__(self, name: str):
if name in self._data:
return self._data[name]
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
def __dir__(self):
# Retrieve standard attributes from the base implementation
default_attrs = super().__dir__()
# Combine default attributes with dynamic keys
dynamic_attrs = list(self._data.keys())
return sorted(set(default_attrs + dynamic_attrs))In this implementation, calling
dir(DynamicRecord({"name": "Alice", "role": "Admin"}))
returns the standard object attributes alongside "name" and
"role", making them immediately available to autocomplete
engines.
Key Rules for Customizing
__dir__
When overriding __dir__, adhere to the following
conventions:
- Return an Iterable of Strings: Python expects
__dir__to return a sequence (typically alist) where every element is a string. Returning invalid types will causedir()to raise aTypeError. - Include Inherited Attributes: Always call
super().__dir__()or incorporateself.__dict__.keys()and class attributes to avoid accidentally hiding built-in methods, properties, and standard utility functions. - Preserve Idempotency and Speed: Because interactive
environments execute
dir()repeatedly during active typing for tab-completion, avoid heavy computations or blocking network calls inside__dir__. If dynamic attributes depend on an external source, cache the keys locally upon object initialization.