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 Custom Attribute Access

Learn how Python customizes instance attribute reads, fallback lookup, assignment, and deletion with __getattribute__, __getattr__, __setattr__, and __delattr__.

Levels: Advanced ~24 min read

Advanced

Python lets a class customize what happens when code reads, writes, or deletes an attribute. The main hooks are __getattribute__ for reads and __setattr__ / __delattr__ for writes and deletion. A separate __getattr__ fallback runs only when normal lookup cannot find the requested attribute.

Intercept every instance attribute read

__getattribute__ is called for every instance attribute access. A common safe pattern is to let the base implementation perform the actual lookup after you add the behavior you need.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class User:
    def __init__(self, name):
        self.name = name

    def __getattribute__(self, name):
        print(f"reading {name}")
        return object.__getattribute__(self, name)

user = User("Ada")
print(user.name)

The hook runs for user.name, then object.__getattribute__() performs the normal lookup.

Avoid recursive lookup inside __getattribute__

Inside __getattribute__, reading another attribute with ordinary dot syntax calls __getattribute__ again. That can recurse forever. Use object.__getattribute__(self, name) when you need the default lookup behavior.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Account:
    def __init__(self, owner):
        self._owner = owner

    def __getattribute__(self, name):
        if name == "owner":
            value = object.__getattribute__(self, "_owner")
            return value.upper()
        return object.__getattribute__(self, name)

account = Account("ada")
print(account.owner)

The custom owner read uses the base lookup instead of self._owner, avoiding another intercepted lookup.

Use __getattr__ as a fallback

__getattr__ is different: it is called only after normal attribute access fails with AttributeError. Existing attributes therefore continue through the normal mechanism.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Settings:
    def __init__(self):
        self.theme = "dark"

    def __getattr__(self, name):
        if name == "timeout":
            return 30
        raise AttributeError(f"{type(self).__name__!s} has no attribute {name!r}")

settings = Settings()
print(settings.theme)
print(settings.timeout)

theme is found normally; timeout is computed only after normal lookup fails.

Understand the order of both read hooks

When both hooks exist, __getattribute__ runs first. If it raises AttributeError and the class defines __getattr__, Python can use the fallback. This makes the two hooks complementary rather than interchangeable.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Config:
    def __getattribute__(self, name):
        print(f"lookup: {name}")
        return object.__getattribute__(self, name)

    def __getattr__(self, name):
        print(f"fallback: {name}")
        return "default"

config = Config()
print(config.missing)

The normal lookup fails first; the fallback then supplies the value.

Customize attribute assignment with __setattr__

__setattr__ is called when an instance attribute assignment is attempted. If you want the normal storage behavior, delegate to object.__setattr__ rather than assigning with ordinary dot syntax inside the hook.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class User:
    def __setattr__(self, name, value):
        if name == "age" and value < 0:
            raise ValueError("age cannot be negative")
        object.__setattr__(self, name, value)

user = User()
user.age = 30
print(user.age)

try:
    user.age = -1
except ValueError as error:
    print(type(error).__name__)

The hook validates the assignment and delegates accepted values to normal instance storage.

Customize attribute deletion with __delattr__

__delattr__ is called for del obj.name. A custom implementation can enforce deletion rules and delegate the actual removal to object.__delattr__.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Session:
    def __init__(self):
        self.token = "abc"

    def __delattr__(self, name):
        if name == "token":
            object.__delattr__(self, name)
            return
        raise AttributeError(f"cannot delete {name!r}")

session = Session()
del session.token
print(hasattr(session, "token"))

Deleting session.token reaches the custom hook, which delegates to the normal deletion behavior.

Use getattr() when the attribute name is dynamic

The built-in getattr() retrieves a named attribute without requiring dot syntax. Its optional default value is useful when a missing attribute should not raise AttributeError. Custom attribute hooks still participate in the lookup.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Profile:
    def __init__(self):
        self.name = "Ada"

    def __getattr__(self, name):
        if name == "nickname":
            return "Ada"
        raise AttributeError(name)

profile = Profile()
print(getattr(profile, "name"))
print(getattr(profile, "nickname", "anonymous"))
print(getattr(profile, "timezone", "UTC"))

getattr() works with names stored in variables or supplied dynamically, while the third argument supplies a fallback value.

Choose the smallest hook that solves the problem

Use __getattr__ when you only need fallback behavior for missing attributes. Use __getattribute__ when you genuinely need to control every instance read. Use __setattr__ or __delattr__ when assignment or deletion itself needs custom rules. In each hook, delegate to the base implementation when you want normal object behavior.

Know the special-method lookup boundary

Custom attribute access is not a universal interception layer for Python. The data model documents cases where implicit special-method lookup can bypass an instance’s __getattribute__ machinery. This matters when debugging why a built-in operation does not produce the same trace as an explicit attribute lookup.

Key takeaways

  • `__getattribute__` is the instance-level hook for attribute reads and runs before normal lookup returns a value.
  • `__getattr__` is a fallback used only when normal attribute lookup raises AttributeError.
  • Use object.__getattribute__() inside a custom getter when you need the default lookup without recursive interception.
  • Use `__setattr__` and `__delattr__` to customize attribute assignment and deletion, delegating to the base implementation for normal storage behavior.
  • Choose the narrowest hook that matches the requirement; broad interception adds complexity and can affect many ordinary attribute operations.
Was this page helpful?

Mark this learning resource complete to track your learning progress.