What does hasattr() hide when a computed attribute raises AttributeError?
answer
- It fetches, it does not inspect
- One exception type disappears silently
- A broken property looks absent
- Python 2 swallowed everything, 3.2 narrowed it
- Check the instance dictionary instead
basics
~20 shasattr actually evaluates the attribute and returns False if the lookup raises AttributeError, including one raised by the attribute's own code. A broken computed attribute is therefore reported as an absent one; other exception types still propagate.
solid answer
~40 s`hasattr(obj, name)` is defined as: perform the lookup, return `False` if it raises `AttributeError`, otherwise `True`. It genuinely *evaluates* the attribute rather than inspecting a table, so a `property` or any other descriptor whose body itself raises `AttributeError` — a lazy load that failed, an inner lookup on a not-yet-populated cache — is indistinguishable from a name that was never defined. Since Python 3.2 only `AttributeError` is caught (Python 2 swallowed every exception), so a `ValueError` or `OSError` from that same code does reach the caller. Three-argument `getattr` swallows identically. The fixes: have computed attributes raise a domain-specific exception rather than `AttributeError`, use `try/except` around the real read when you want the error, or check `name in vars(obj)` when you mean "is there an instance attribute here".
code
python · 16 linesclass Transcript:
@property
def participants(self):
raise AttributeError("backing store not loaded")
@property
def duration(self):
raise ValueError("clock skew")
t = Transcript()
print(hasattr(t, "participants"))
print(getattr(t, "participants", "missing"))
try:
hasattr(t, "duration")
except ValueError as exc:
print("propagates:", exc)go deeper
Know that hasattr returns a boolean and that it actually fetches the attribute rather than looking a name up in a list. Be able to say which single exception type it converts into False.
Explain why a False is ambiguous: the guard cannot distinguish an undefined name from a computed attribute whose own body raised AttributeError, and the same hole exists in three-argument getattr.
Show the production consequence — records shipping with quietly missing fields and no traceback — and the fixes: never raise AttributeError for a load failure, and prefer a single guarded read over check-then-use.
Frame the convention: one exception type carrying both 'no such name' and 'that code failed' is a contract problem, so decide what your codebase's computed attributes are allowed to raise and enforce it in review.
## hasattr runs code The common mental model is that `hasattr(obj, name)` asks a table whether a name is present. It does not. Its definition is operational: ```python try: getattr(obj, name) except AttributeError: return False return True ``` The attribute is *fetched*. For a plain instance attribute that is harmless. For anything computed — a `property`, a cached-lookup descriptor, a lazily materialised field — the code behind the name runs to completion, with all of its cost and all of its side effects, and its value is then thrown away. Two distinct problems fall out of that, and a good answer names both. ## Problem one: a broken attribute looks like a missing one Because the guard catches `AttributeError` without caring where it came from, an `AttributeError` raised *inside* the attribute's own body is reported as the attribute not existing: ```python class Transcript: @property def participants(self): raise AttributeError("backing store not loaded") hasattr(Transcript(), "participants") # False ``` Nothing about that `False` says a bug was swallowed. And the family is wider than a deliberately-raised error: any bug in the property body that itself surfaces as an `AttributeError` — calling a method on something that turned out to be `None`, a typo'd inner attribute name, a chained access through an object that failed to load — comes back to the caller as "the attribute is not there". Code guarded with `if hasattr(...)` then takes the it-is-absent branch, and the failure surfaces far downstream as missing data rather than as a traceback pointing at the real line. Three-argument `getattr` has exactly the same hole for exactly the same reason, so `getattr(obj, name, None)` is not a workaround. ## Problem two: it is not free, and it is not idempotent The `hasattr`-then-read shape computes the attribute twice: ```python if hasattr(obj, "summary"): use(obj.summary) # the property body runs a second time ``` If the attribute caches its result, the second run is cheap. If it does not — it queries something, reads a file, or advances an iterator — you paid twice, and any side effect happened twice. That is why the Python idiom for optional attributes is EAFP rather than LBYL: do the read inside `try/except AttributeError`, which performs one lookup, or use three-argument `getattr` when a default is genuinely acceptable and you have accepted the swallow above. ## What changed, and what did not On Python 2, `hasattr` caught *everything*: a `ValueError` or an `OSError` from a property also produced `False`. Python 3.2 narrowed it to `AttributeError` only, and that is still the behaviour on 3.14. So today the distinction is sharp and worth stating precisely in an interview: **the exception type decides**. A `ValueError` raised inside a property propagates out of `hasattr` and crashes the guard; an `AttributeError` becomes a quiet `False`. ## Doing it correctly The first fix is upstream, in the attribute itself: **do not raise `AttributeError` from a computed attribute to signal a failure.** Raise something the domain owns — a load error, a `RuntimeError`, a project-specific exception. Reserve `AttributeError` for the one thing the protocol means by it: this object does not provide this name. A lazy loader that raises `AttributeError` when its backing store is unavailable is actively lying to every reflective caller in the process. The second is choosing the right question at the call site. If you want to know whether the *instance* carries a stored attribute, ask the instance's dictionary directly — `name in vars(obj)` — which reads a mapping and runs no user code, at the cost of not seeing class attributes or computed ones. If you want to know whether the *type* declares the name at all without triggering it, `inspect.getattr_static(obj, name)` retrieves the raw attribute without invoking the descriptor protocol, so a `property` comes back as the `property` object itself rather than as its computed value. And if you actually want the value, take it with `try/except AttributeError` around the direct read, so the exception object is in hand for logging instead of collapsed into a boolean. ## The interview signal What separates a middle answer from a junior one here is not knowing that `hasattr` exists but being able to say *why* a `False` from it is ambiguous, and that the ambiguity is not a wart of the builtin but a direct consequence of using one exception type for both "no such name" and "the code behind that name failed". The senior version adds the operational half: in a long-running process, this class of swallow does not crash, it degrades — records ship without fields, and nothing in the logs points at the property that broke.
- How would you tell a genuinely absent attribute from a computed one that blew up?Read it directly inside `try/except AttributeError` and inspect the exception — the traceback points at the property body when the failure came from inside it. To avoid running the code at all, `inspect.getattr_static` fetches the raw attribute without invoking the descriptor protocol, and `name in vars(obj)` answers the narrower question of whether the instance itself stores the name.
- Does hasattr on an expensive lazy attribute cost you anything?Yes. It executes the attribute's code and discards the result, so the common `if hasattr(...)` then read shape computes it twice and repeats any side effect. Only a caching attribute makes the second run cheap. That is a large part of why Python idiom prefers a single `try/except AttributeError` read over the check-then-use pair.
- What should a lazy loader raise when its backing store is unavailable?Anything but `AttributeError`. A domain-specific exception, or `RuntimeError`, keeps the failure visible; `AttributeError` is the protocol's word for "this object does not provide that name", so raising it for a load failure makes every reflective caller — `hasattr`, three-argument `getattr`, name-driven serializers — silently classify a broken field as an absent one.
saying these in an interview costs you the question
- Thinks hasattr inspects a table and runs no code
- Says hasattr returns False for any exception raised
- Uses hasattr then reads, computing an expensive attribute twice
- Assumes hasattr only consults the instance dictionary
- Raises AttributeError from a property to signal a load failure
- Believes three-argument getattr avoids the same swallow