When defining a descriptor class, you can simply raise an AttributeError
to signal that the attribute isn't available. For example:
from typing import Any, TypeVar, MutableMapping
V = TypeVar("V")
class Desc:
def __init__(self, key: str) -> None:
self.key = key
def __get__(self, instance: Any, owner: type) -> V:
try:
return instance._dict[self.key]
except KeyError:
raise AttributeError()
class C:
foo = Desc("d")
def __init__(self, d: MutableMapping[str, V]) -> None:
self._dict = d
Use like:
>>> d1 = dict(d=0, bar=0, baz=0)
>>> c = C(d1)
>>> c.foo
0
>>> d1.update(d=1)
>>> c.foo
1
>>> hasattr(C(dict()), "foo")
False
Note that the AttributeError
causes the hasattr
function to "fail silently". This is as expected and described by the descriptor protocol.
However, in some cases you may want the AttributeError
to "bubble up" to the top. For example:
class NewDesc:
def __get__(self, instance, owner):
do_complex_task(instance) # <-- AttributeError occurs here
return instance._on_the_fly_private_attr
I have found that this is easily handled by wrapping the offending line of code in a try-except block that raises some other error different than AttributeError
:
try:
do_complex_task(instance) # <-- AttributeError occurs here
except AttributeError as e:
raise MyError() from e
Is this the canonical way of handling this problem? What pitfalls am I leaving myself open to by doing it this way?