Why does a proxy that forwards attributes via `__getattr__` still raise TypeError on `with proxy:`?
answer
- The hook belongs to the other lookup path
- Protocols are resolved on the type
- __getattribute__ would not help either
- object already supplies some of them silently
- Declare each dunder on the proxy class
basics
~20 sImplicit invocations skip getattribute and getattr completely: the with statement resolves enter and exit on type(proxy). The forwarding hook is never consulted, so a proxy must declare on its own class every special method it wants to support.
solid answer
~40 s`__getattr__` is part of ordinary attribute lookup, and protocols do not use ordinary attribute lookup. `with proxy:` resolves `__enter__` and `__exit__` by searching the MRO of `type(proxy)` directly, so a hook that would have forwarded them is never reached and you get a `TypeError` saying the object does not support the context manager protocol. The same applies to `len()`, iteration, subscripting and every operator. Worse, the failures are inconsistent: `object` already defines `__str__`, `__repr__`, `__eq__` and `__hash__`, so those silently use the proxy's own identity-based defaults instead of raising, and since `object` defines no `__bool__` and the proxy has no `__len__`, `bool(proxy)` is unconditionally `True`. The fix is to declare each forwarded dunder on the proxy class, usually generated from a list of names.
code
python · 15 linesclass Proxy:
def __init__(self, wrapped):
self._wrapped = wrapped
def __getattr__(self, name):
return getattr(self._wrapped, name)
p = Proxy([1, 2, 3])
print(p.count(2)) # 1 — an ordinary attribute, forwarded by __getattr__
try:
len(p)
except TypeError as exc:
print(exc) # object of type 'Proxy' has no len()
print(bool(p)) # True — object defines no __bool__, so nothing failsgo deeper
Take away the practical rule: wrapping an object and forwarding attributes does not give the wrapper the original's behaviour under len(), in, with or operators. Those must be written on the wrapper's class.
Explain why the hook is unreachable — __getattr__ is the tail of ordinary attribute lookup, and protocols resolve on the type — and show the fix of declaring or generating each forwarded special method as a class attribute.
Diagnose the silent half: which dunders object already supplies, why bool() on the wrapper is always true, why a hand-written __exit__ that returns the wrong thing suppresses exceptions, and how you would test a proxy's protocols deliberately.
Decide whether a transparent proxy is the right tool at all. Argue the boundary — which protocols the wrapper promises, what forwarding __class__ or __hash__ costs in correctness — and prefer an explicit, narrow interface over a wrapper that pretends to be the original.
A forwarding proxy is the canonical place this rule draws blood, because the proxy looks like it works. ## The scenario A geocoding batch job builds its client lazily: constructing it costs a 45-second cold start, and most runs of the job never need it. So the client is wrapped in a proxy that builds the real object on first use and forwards everything else: ```python class LazyClient: def __init__(self, factory): self._factory = factory self._real = None def _get(self): if self._real is None: self._real = self._factory() return self._real def __getattr__(self, name): return getattr(self._get(), name) ``` `proxy.geocode(address)` works. `proxy.timeout` works. Then someone writes `with proxy:` to scope the client's session and gets a `TypeError` saying the object does not support the context manager protocol — for an object that demonstrably forwards everything. ## Why the hook is never reached `__getattr__` is the last step of *ordinary* attribute lookup, run by `object.__getattribute__` when nothing else matched. The `with` statement does not perform ordinary attribute lookup. Like every protocol, it uses implicit special method lookup: the interpreter searches the MRO of `type(proxy)` for `__enter__` and `__exit__`, applies the descriptor protocol, and calls what it finds. The instance dictionary is skipped and so are `__getattribute__` and `__getattr__` — at every level, including on a metaclass. There is no interception point. `len(proxy)`, `iter(proxy)`, `proxy[0]`, `proxy + 1` and `-proxy` all fail the same way. And overriding `__getattribute__` instead does not help, for exactly the same reason: implicit lookup does not call it either. ## The dangerous half: the ones that do not fail If every protocol raised, the bug would be found in five minutes. It does not, because `object` sits at the root of every MRO and already defines several dunders. `str(proxy)` and `repr(proxy)` find `object.__str__`/`object.__repr__` and print `<__main__.LazyClient object at 0x...>` instead of the wrapped object's representation — so logs quietly describe the wrapper. `proxy == other` finds `object.__eq__` and compares identity. `hash(proxy)` hashes the wrapper. And `object` defines no `__bool__`, while the proxy has no `__len__`, so `bool(proxy)` is unconditionally `True`: a truthiness check on a wrapped empty collection silently takes the wrong branch. The geocoding batch fails in the sharpest version of this. Someone hand-writes `__exit__` on the proxy to make `with` work and returns the wrapped object's method by mistake instead of calling it: ```python def __exit__(self, *exc_info): return self._get().__exit__ # BUG: a bound method, not its result ``` A bound method is truthy, and a truthy `__exit__` return *suppresses the exception*. Every failure inside the `with` block — a bad address, a timeout, a quota error — is swallowed; the batch exits zero and reports success while whole regions are unresolved. The rule that made the proxy incomplete then made the incomplete fix silent. ## Doing it properly Declare each special method on the proxy *class*, and generate them rather than writing thirty by hand: ```python FORWARDED = ("__len__", "__iter__", "__contains__", "__str__", "__enter__", "__exit__") def _forward(name): def method(self, *args): return getattr(self._get(), name)(*args) method.__name__ = name return method for _name in FORWARDED: setattr(LazyClient, _name, _forward(_name)) ``` Points of craft. Choose the list explicitly — a proxy that forwards `__class__` will lie to `isinstance`, and one that forwards `__hash__` while forwarding `__eq__` inconsistently corrupts dictionaries. Forward the protocols you actually promise. Return the *result* of the wrapped call, never the callable. Remember that a dunder is looked up on the type, so this generation must run at class creation (a loop after the `class` statement, a decorator, `__init_subclass__` or a metaclass), never inside `__init__`. In the standard library, `weakref.proxy` implements this forwarding in C over a broad set of slots, which is a useful existence proof but is tied to weak references and not a general wrapper. Finally, test the protocols explicitly. A proxy's test suite that only calls methods will pass forever while `bool()`, `len()`, `in`, `str()` and `with` are all quietly wrong. ## The interview shape of this The question is usually posed as a puzzle — "this proxy forwards everything, why does `with` fail?" — and what is being assessed is whether you reach for the object model instead of guessing at the wrapped object. A complete answer names the two lookup paths, says that `__getattribute__` is no better an interception point than `__getattr__`, distinguishes the protocols that raise from the ones `object` quietly satisfies, and ends on the design question of how transparent a wrapper should try to be. Transparency is a spectrum: a proxy that forwards a handful of named protocols is honest and debuggable, while one that also forwards `__class__` to fool `isinstance` is a wrapper that lies to every tool that inspects it, including your own logging and your test doubles. Where a proxy exists purely to defer construction, the smaller answer is often better — hand callers an explicit accessor and keep the wrapper out of the protocol business entirely.
- Which operations on such a proxy misbehave silently instead of raising?The ones `object` already defines: `str()`, `repr()`, `==` and `hash()` find `object`'s identity-based versions and describe or compare the wrapper. `bool(proxy)` is the nastiest — `object` defines no `__bool__` and the proxy has no `__len__`, so it is always `True` even when the wrapped object is empty.
- Would overriding `__getattribute__` instead of `__getattr__` catch the special methods?No. Implicit special method lookup bypasses `__getattribute__` too — it reads the type's MRO directly and applies the descriptor protocol. Overriding it only makes ordinary attribute access slower and more fragile. The special methods have to exist as class attributes on the proxy.
- Why must the generated dunders be installed at class creation rather than in `__init__`?Because the lookup resolves on the type. Anything `__init__` sets lands in the instance dictionary, where implicit lookup never looks. Install them with a loop after the class statement, a class decorator, `__init_subclass__` or a metaclass — all of which write class attributes.
saying these in an interview costs you the question
- Says __getattr__ runs for every attribute access, dunders included
- Proposes overriding __getattribute__ to intercept the protocols
- Sets the forwarded dunders on the instance inside __init__
- Returns a truthy value from __exit__, silently suppressing errors
- Assumes bool(proxy) reflects the wrapped object's truthiness
- Thinks wrapping an object inherits its protocols automatically