Card 06/ 09
Proxy Classes vs __getattr__ in Python
One method forwards every attribute the proxy does not define, instead of one method per call.
Python can write a proxy without listing every method: __getattr__ is called whenever normal lookup fails to find an attribute, so one method can forward everything the proxy does not define itself. This card compares that form with the explicit class of the template, and shows the two things it does not forward.
An explicit proxy classeach method written out
class LoggingInbox(Inbox):
def __init__(self, target):
self._target = target
def unread(self):
print("proxy: forwarding unread")
return self._target.unread()Every method of the interface needs a forwarding method, and a type checker sees all of them.
A __getattr__ proxyforwarding by name
class LoggingProxy:
def __init__(self, target):
self._target = target
def __getattr__(self, name):
print(f"proxy: forwarding {name}")
return getattr(self._target, name)One method forwards any attribute, for any target, including methods added later.
class Inbox:
def __init__(self) -> None:
self._messages = ["hello", "invoice"]
def unread(self) -> int:
return len(self._messages)
def __len__(self) -> int:
return len(self._messages)
class LoggingProxy:
def __init__(self, target: object) -> None:
self._target = target
def __getattr__(self, name: str):
print(f"proxy: forwarding {name}")
return getattr(self._target, name)
inbox = LoggingProxy(Inbox())
print(inbox.unread())
print(inbox.__len__())
try:
print(len(inbox))
except TypeError as error:
print(f"TypeError: {error}")proxy: forwarding unread
2
proxy: forwarding __len__
2
TypeError: object of type 'LoggingProxy' has no len()The first two calls go through __getattr__, because LoggingProxy has no unread or __len__ of its own. The third fails: len() looks up __len__ on the object's class, not the object, and so never triggers __getattr__. Every special method behaves this way, __iter__, __eq__ and __getitem__ included, so a __getattr__ proxy over a list cannot be iterated or indexed until those methods are written out.
The second loss is type checking. A type checker cannot know what __getattr__ will return, so a LoggingProxy is not a Subject to it, and misspelt method names are found only when the code runs. Use __getattr__ for a quick, general proxy such as logging in a test, and an explicit class when the proxy is part of a typed interface or must support special methods.
← → move through sections and cards