Both Proxy and State provide a surrogate class that changes what happens behind a call without changing the calling code. The surrogate hides the implementing class that does the work. When you call a method in the surrogate, the surrogate calls that method in the implementing class. The two patterns are so similar that Proxy is a special case of State.
From a base class, derive the surrogate along with the class or classes that provide the implementation:
This is the shape in GoF Design Patterns. Python does not need the shared base, but the base is the clearest way to see what a surrogate is.
A surrogate goes wherever an implementation goes. A surrogate object receives an implementation and forwards all method calls to it. Both Proxy and State use that indirection: the surrogate can refuse a call, delay creating the implementation, count or log the calls it forwards, or swap the implementation for another.
Structurally, Proxy and State differ in one respect. A Proxy forwards to one implementation for its whole life. State holds several and switches among them.
Here the Proxy drops the shared base and forwards each call by hand:
# proxy_forwarding.py
class Proxy:
def __init__(self, impl: Implementation) -> None:
self.__implementation = impl
# Pass method calls to the implementation:
def f(self) -> None: self.__implementation.f()
def g(self) -> None: self.__implementation.g()
class Implementation:
def f(self) -> None:
print("Implementation.f()")
def g(self) -> None:
print("Implementation.g()")
p = Proxy(Implementation())
p.f()
#: Implementation.f()
p.g()
#: Implementation.g()Implementation need not have the same interface
as Proxy. Proxy qualifies as long as
code calls it where it would otherwise call the implementation.
That is a looser definition than in GoF Design
Patterns, and relies only on intent. Under GoF’s stricter
definition, the interface separates Proxy from
Adapter. Telling
the Wrappers Apart clarifies both readings.
A common interface helps, though: Implementation
must then supply every method that Proxy calls. One
way to express that interface is an abstract base class. Each
method the Proxy delegates to is an
@abstractmethod, so you cannot instantiate an
implementation that omits one:
# proxy_interface.py
from abc import ABC, abstractmethod
from typing import override
class Service(ABC):
@abstractmethod
def f(self) -> None: ...
@abstractmethod
def g(self) -> None: ...
class Proxy(Service):
def __init__(self, service: Service) -> None:
self.__service = service
@override
def f(self) -> None: self.__service.f()
@override
def g(self) -> None: self.__service.g()
class Complete(Service):
@override
def f(self) -> None: print("Complete.f()")
@override
def g(self) -> None: print("Complete.g()")
class Partial(Service): # Missing g()
@override
def f(self) -> None: print("Partial.f()")
p = Proxy(Complete())
p.f()
#: Complete.f()
p.g()
#: Complete.g()
try:
Proxy(Partial())
except TypeError as e:
print(str(e).partition(" without")[0])
#: Can't instantiate abstract class PartialBecause Proxy accepts any Service
and Complete implements both methods, the proxy can
forward either call. Because Partial omits
g(), constructing a Partial raises a
TypeError before the first call. The inheritance
makes a Proxy acceptable wherever code expects a
Service, and the type checker verifies the
Proxy’s f() and g()
against the base.
A Protocol
is the structural alternative: the implementation needs no base
class. The type checker verifies the shape statically, and
@runtime_checkable lets isinstance()
check the shape at runtime:
# proxy_protocol.py
from typing import Protocol, runtime_checkable
@runtime_checkable # Allows isinstance() on a Protocol
class Service(Protocol):
def f(self) -> None: ...
def g(self) -> None: ...
class Complete: # Conforms without inheriting Service
def f(self) -> None: print("Complete.f()")
def g(self) -> None: print("Complete.g()")
class Partial: # Missing g()
def f(self) -> None: print("Partial.f()")
print(isinstance(Complete(), Service))
#: True
print(isinstance(Partial(), Service))
#: FalseWith inheritance, the abstract base class rejects an
incomplete implementation at construction. A
Protocol instead reports the mismatch statically,
at a parameter annotated Service, and needs no
common base. One caveat: isinstance() against a
@runtime_checkable Protocol checks only that the
methods exist, not that their signatures match. The static type
checker verifies signatures.
__getattr__()__getattr__() is Python’s built-in delegation
mechanism, which Singleton used to reach
its inner object. Delegating through it makes Proxy
simpler to implement:
# proxy_getattr.py
from typing import Any
class Proxy:
def __init__(self, impl: Any) -> None:
self.__implementation = impl
def __getattr__(self, name: str) -> Any:
return getattr(self.__implementation, name)
class Implementation:
def f(self) -> None:
print("Implementation.f()")
def g(self) -> None:
print("Implementation.g()")
def h(self) -> None: # New; Proxy needs no change
print("Implementation.h()")
p = Proxy(Implementation())
p.f()
#: Implementation.f()
p.g()
#: Implementation.g()
p.h()
#: Implementation.h()__getattr__() makes the forwarding generic:
because Proxy names no methods in
Implementation, it keeps working when you add a
method to the implementation. Implementation here
has an h() that proxy_forwarding.py’s
lacked, and p.h() forwards it without changing
Proxy.
The double underscore on self.__implementation
matters: the name mangles
to _Proxy__implementation, so it cannot collide
with an attribute the implementation defines.
Do not confuse __getattr__() with its lookalike,
__getattribute__(). __getattr__() is
the fallback hook: Python calls it only after normal
lookup fails. Normal lookup finds
self.__implementation, so reading that name in the
hook’s body does not call the hook again.
__getattribute__() intercepts every attribute
access, including each self. access in its own
body, so the naive version calls itself forever. Writing a
__getattribute__() means calling
object.__getattribute__() for every internal
access, machinery a surrogate rarely needs.
proxy_interface.py’s
abstract base class still guards the implementation side: its
Proxy takes a Service parameter, so
the type checker verifies that whatever you provide has the
necessary methods. A Protocol on that parameter
guards it structurally, and proxy_protocol.py’s
isinstance() is the runtime half of that check.
Calls on the proxy get no such check. Because
__getattr__() resolves p.f() and
returns Any, the checker cannot verify that call.
With explicit forwarding, as in proxy_forwarding.py,
p.f() reaches a declared method with a declared
return type, and the checker verifies the call. proxy_interface.py’s
Proxy also passes as a Service:
because it inherits Service, code typed against
Service accepts it. __getattr__()
gives up that check so it can forward every method, including
ones added later.
The lost static check is the first of five limits on
__getattr__() delegation. The next four sections
cover the rest: Python never calls __getattr__()
for a special-method lookup or for an assignment,
__getattr__() calls itself when the name it reads
is also missing, and a surrogate that supplies its methods
through __getattr__() fails an
isinstance() check.
__getattr__()Python looks up dunders like __len__() and
__str__() on the proxy’s type, not on the instance,
so len(p) and print(p) do not
delegate, even though an explicit p.__len__()
does:
# dunder_bypass.py
from typing import Any
from exceptions import expect
class Proxy:
def __init__(self, impl: Any) -> None:
self.__implementation = impl
def __getattr__(self, name: str) -> Any:
return getattr(self.__implementation, name)
class Words:
def __init__(self) -> None:
self.items = ["spam", "eggs"]
def __len__(self) -> int:
return len(self.items)
p = Proxy(Words())
print(p.__len__()) # The explicit call delegates
#: 2
# Special-method lookup skips the instance:
expect(TypeError, len, p) # type: ignore
#: [TypeError] object of type 'Proxy' has no len()
print("__main__.Proxy object" in str(p))
#: TrueThe two calls look interchangeable and are not.
p.__len__() is ordinary attribute access, so the
failed instance lookup falls through to
__getattr__(), which delegates. len(p)
looks up __len__() on type(p), skips
the instance, finds none, and reports that Proxy
has no len(). ty and Pyright reject
len(p) statically for the same reason, so the
listing needs the # type: ignore to show the
runtime failure. Under mypy, __getattr__() also
satisfies the lookup for __len__(), so
len(p) passes the check and fails only at runtime.
A proxy that must forward special methods defines them
explicitly.
len(p) reports the missing method because
object defines no __len__().
print(p) reports no missing method:
object defines __str__(), so the
lookup on type(p) finds object’s
__str__() and the proxy prints as a
Proxy object. Whenever object defines
the dunder, the bypass raises no error. The proxy answers with
object’s version, and the call never reaches the
implementation.
Delegation using __getattr__() forwards reads
but not writes:
# proxy_writes.py
from typing import Any
class Proxy:
def __init__(self, impl: Any) -> None:
self.__implementation = impl
def __getattr__(self, name: str) -> Any:
return getattr(self.__implementation, name)
class Settings:
def __init__(self) -> None:
self.level = "low"
settings = Settings()
p = Proxy(settings)
print(p.level)
#: low
p.level = "high" # type: ignore
print(p.level, settings.level)
#: high low__getattr__() is a read hook: Python calls it
for a failed read, never for an assignment. The assignment
stores level in the proxy’s __dict__,
not the implementation’s __dict__. The next
p.level lookup succeeds without calling
__getattr__(). The proxy reports
"high" and the implementation reports
"low". The type checker rejects the assignment
because Proxy declares no level and no
__setattr__() that would accept one.
To forward writes, define __setattr__().
__setattr__() then intercepts every assignment,
including the one in __init__(). Forwarding that
first assignment would recurse, because the implementation does
not exist yet, so __init__() stores the
implementation another way:
# proxy_setattr.py
from typing import Any
class WriteProxy:
def __init__(self, impl: Any) -> None:
object.__setattr__(self, "_implementation", impl)
def __getattr__(self, name: str) -> Any:
return getattr(self._implementation, name)
def __setattr__(self, name: str, value: Any) -> None:
setattr(self._implementation, name, value)
class Settings:
def __init__(self) -> None:
self.level = "low"
settings = Settings()
p = WriteProxy(settings)
p.level = "high"
print(p.level, settings.level)
#: high highobject.__setattr__() stores
_implementation on the proxy without going through
__setattr__(). Now every assignment after
__init__() reaches the implementation via the new
__setattr__(), so the proxy and the implementation
report the same value. WriteProxy needs no
# type: ignore, because a declared
__setattr__() makes the type checker accept
assignment to any attribute name.
The implementation attribute no longer needs a double
underscore. Mangling rewrites identifiers, not string literals,
so storing a double-underscore name through
object.__setattr__() would mean writing the mangled
form, "_WriteProxy__implementation", by hand.
The fallback hook __getattr__() can recurse. If
__getattr__()’s body reads a proxy attribute that
does not exist, the failed lookup calls
__getattr__() again. Python reports this as a
RecursionError, not the AttributeError
that would name the cause.
A misspelled self._implementation is one cause.
Rebuilding a proxy through copy.copy() or
pickle is another: both construct the new instance
without calling __init__(), so no
_implementation exists when the first failed lookup
calls __getattr__(). The fix is a guard at the top
of __getattr__() that raises
AttributeError for any name that starts with an
underscore:
# getattr_guard.py
from typing import Any
class Proxy:
def __init__(self, impl: Any) -> None:
self._implementation = impl
def __getattr__(self, name: str) -> Any:
if name.startswith("_"): # The guard
raise AttributeError(name)
return getattr(self._imp, name) # Deliberate typo
class Implementation:
def f(self) -> None: print("Implementation.f()")
try:
Proxy(Implementation()).f()
except AttributeError as e:
print(type(e).__name__, e)
#: AttributeError _impWithout the guard, the misspelled self._imp
produces a RecursionError that names nothing. With
the guard, the second call to __getattr__() reports
the typo by name. The guard also makes the proxy work with
copy and pickle, which look up
__setstate__() before __init__() has
run. Both get an AttributeError, which those
modules handle, instead of recursing.
This chapter’s other __getattr__() proxies do
not include the guard, so each listing shows one idea.
A proxy is not an instance of the implementation’s class.
Delegation forwards the methods, not the type, and
isinstance() checks only the proxy’s own class. A
@runtime_checkable Protocol does not
change that. Since Python 3.12 the Protocol check uses
inspect.getattr_static(), which reads the class and
instance dictionaries instead of running attribute lookup. That
function never calls __getattr__(), so a proxy that
supplies every method through __getattr__() also
fails the isinstance() check. Because ordinary
attribute access still finds those methods,
hasattr(p, "f") is True and
p.f() runs. Code that calls the method, or checks
with hasattr(), works on a surrogate, as long as
__getattr__() raises only
AttributeError for a name it does not have. The
protection proxy below raises PermissionError
instead, and hasattr() catches only
AttributeError, so hasattr()
propagates that exception rather than returning
False.
# proxy_identity.py
from typing import Any, Protocol, runtime_checkable
@runtime_checkable
class Service(Protocol):
def f(self) -> None: ...
class Proxy:
def __init__(self, impl: Any) -> None:
self.__implementation = impl
def __getattr__(self, name: str) -> Any:
return getattr(self.__implementation, name)
class Implementation:
def f(self) -> None: print("Implementation.f()")
p = Proxy(Implementation())
p.f()
#: Implementation.f()
print(hasattr(p, "f"))
#: True
print(isinstance(p, Implementation), isinstance(p, Service))
#: False FalseThe call works and hasattr() finds the method,
yet both isinstance() checks return
False.
Two workarounds make isinstance() return
True, and neither verifies anything:
Service.register(Proxy) tells the ABC machinery
to answer True for every Proxy,
without looking at its methods.__class__ property returning the
implementation’s class makes isinstance() see that
class rather than Proxy.Both satisfy the runtime check and neither satisfies a type
checker. Inheritance satisfies both checks. proxy_interface.py’s
Proxy inherits Service, so
isinstance(p, Service) returns True,
and the type checker confirms that Proxy’s
f() and g() match
Service. Forwarding through
__getattr__() gives up both. A surrogate is not its
implementation, and code that checks with
isinstance() should check for the method
instead.
GoF Design Patterns lists these common uses for Proxy:
The standard library’s own weakref.proxy() is a
transparent forwarding wrapper too, but it solves none of these
four: it forwards to a weakly referenced object and raises
ReferenceError once nothing else holds a strong
reference to that object. Cleanup
uses weakref.ref(), and a
WeakValueDictionary in the section that follows,
both from the same module and neither needing this one.
A Virtual proxy delays building an expensive object until something asks for it:
# virtual_proxy.py
from typing import Any
class Expensive:
def __init__(self) -> None:
print("Expensive built")
def query(self) -> str:
return "result"
class Lazy:
def __init__(self) -> None:
self._real: Expensive | None = None
def __getattr__(self, name: str) -> Any:
if self._real is None:
self._real = Expensive()
return getattr(self._real, name)
p = Lazy()
print("proxy ready")
#: proxy ready
print(p.query())
#: Expensive built
#: resultBuilding Lazy prints nothing.
__getattr__() builds Expensive on the
first forwarded access, and every later access reuses that same
instance.
A Protection proxy decides whether a call reaches
the implementation. Because __getattr__() receives
the requested name, the check is one condition:
# protection_proxy.py
from typing import Any, Final
READ_ONLY: Final[frozenset[str]] = frozenset({"read"})
class Guarded:
def __init__(self, doc: Document, *,
admin: bool) -> None:
self._doc = doc
self._admin = admin
def __getattr__(self, name: str) -> Any:
if not self._admin and name not in READ_ONLY:
raise PermissionError(name)
return getattr(self._doc, name)
class Document:
def read(self) -> str: return "contents"
def erase(self) -> None: print("erased")
guest = Guarded(Document(), admin=False)
print(guest.read())
#: contents
try:
guest.erase()
except PermissionError as e:
print(type(e).__name__, e)
#: PermissionError erase
try:
hasattr(guest, "erase")
except PermissionError as e:
print(type(e).__name__, e)
#: PermissionError erase
Guarded(Document(), admin=True).erase()
#: erasedGuarded requires admin privileges
to call erase(). hasattr() catches
only AttributeError.
guest.__getattr__() raises
PermissionError instead, so
hasattr(guest, "erase") does not return
False, it raises PermissionError too.
A surrogate whose __getattr__() can raise something
other than AttributeError breaks
hasattr(). It fails isinstance() for a
different reason: as A Surrogate Is Not
Its Implementation explains, the Protocol check uses
inspect.getattr_static(), which never calls
__getattr__().
A Smart reference proxy adds behavior around each
access. With __getattr__() you can wrap every
method call, for example to count them. This proxy names its
implementation _impl, with one underscore, and so
gives up the mangling that kept proxy_getattr.py’s
attribute from colliding. _impl and
calls now share a namespace with the
implementation’s own attributes: reading calls from
the proxy gives the counter, even when the implementation
defines a calls of its own.
# counting_proxy.py
from typing import Any
class CountingProxy:
def __init__(self, impl: Any) -> None:
self._impl = impl
self.calls = 0
def __getattr__(self, name: str) -> Any:
attr = getattr(self._impl, name)
if callable(attr):
def counted(*args: Any, **kwargs: Any) -> Any:
self.calls += 1
return attr(*args, **kwargs)
return counted
return attr
class Implementation:
def f(self) -> None: print("f()")
def g(self) -> None: print("g()")
if __name__ == "__main__":
p = CountingProxy(Implementation())
p.f()
p.g()
p.f()
print(p.f is p.f, p.f.__name__)
print("calls:", p.calls)
#: f()
#: g()
#: f()
#: False counted
#: calls: 3__getattr__() returns a value, and here that
value is a new function. counted closes over
attr, the implementation’s bound method, so the
tally advances at the call, not at the lookup. An attribute that
is not callable returns unchanged, so reading a data attribute
counts nothing.
Lookup fails on every p.f, so every access
builds another counted. Two lookups of the same
name therefore produce two different objects, and the wrapper
reports its own name rather than the implementation’s. Building
a counted increments nothing. Only calling performs
the increment, so the three lookups in the print()
leave the tally at three.
Python calls __getattr__() for any name the
proxy and its class lack, but not for the proxy’s own
attributes. The proxy therefore names no method of the
implementation while still keeping state of its own. The same
few lines serve lazy initialization (a virtual proxy),
access checks (a protection proxy), or call tracking (a
smart reference), over any object.
Testing confirms that a call reaches the implementation and returns its result, and that the proxy counts calls without counting an attribute read:
# test_counting_proxy.py
from counting_proxy import CountingProxy
class Doubler:
def double(self, n: int) -> int:
return n * 2
def test_proxy_forwards_call_and_result() -> None:
p = CountingProxy(Doubler())
assert p.double(5) == 10
assert p.double(3) == 6
def test_proxy_counts_only_calls() -> None:
class HasValue:
answer = 42
p = CountingProxy(HasValue())
# Non-callable attribute passes through
assert p.answer == 42
p2 = CountingProxy(Doubler())
p2.double(1)
p2.double(1)
assert p.calls == 0
assert p2.calls == 2The State pattern adds more implementations to Proxy, along with a way to switch implementations during the surrogate’s lifetime:
# state_surrogate.py
from typing import Any, Protocol
class Behavior(Protocol):
def f(self) -> None: ...
def g(self) -> None: ...
def h(self) -> None: ...
class Surrogate:
def __init__(self, implementation: Any) -> None:
self.__implementation = implementation
def change_to(self, new_implementation: Any) -> None:
self.__implementation = new_implementation
# Delegate calls to the implementation:
def __getattr__(self, name: str) -> Any:
return getattr(self.__implementation, name)
class Implementation1:
def f(self) -> None:
print("Fiddle de dum, Fiddle de dee,")
def g(self) -> None:
print("Eric the half a bee.")
def h(self) -> None:
print("Ho ho ho, tee hee hee,")
class Implementation2:
def f(self) -> None:
print("We're Knights of the Round Table.")
def g(self) -> None:
print("We dance whene'er we're able.")
def h(self) -> None:
print("We do routines and chorus scenes")
def run(b: Any) -> None:
b.f()
b.g()
b.h()
b.g()
if __name__ == "__main__":
first: Behavior = Implementation1()
second: Behavior = Implementation2()
b = Surrogate(first)
run(b)
b.change_to(second)
run(b)
#: Fiddle de dum, Fiddle de dee,
#: Eric the half a bee.
#: Ho ho ho, tee hee hee,
#: Eric the half a bee.
#: We're Knights of the Round Table.
#: We dance whene'er we're able.
#: We do routines and chorus scenes
#: We dance whene'er we're able.run() never changes and neither does
b. Only the surrogate’s current implementation
changes. Here the client programmer calls
change_to(), but in a State Machine, each
implementation chooses its own successor, so the surrogate
advances without the client asking. change_to()
reassigns __implementation with no lock. While one
thread runs a multi-call sequence like run(),
another thread’s change_to() can run between two of
those calls, splitting the sequence across both implementations;
see Concurrency
for what an unsynchronized swap costs.
The annotations that carry the implementation are all
Any, which the book’s typing guidance treats as a
last resort, and the two uses have different reasons.
run(b: Any) has no alternative. Annotating
run(b: Behavior) and passing it b is a
type error, because Surrogate defines no
f() of its own. As Forwarding with
__getattr__() explains, the checker cannot
verify a method that __getattr__() supplies.
Surrogate.__init__() and
change_to() are a choice. state_surrogate.py
successfully type-checks when both parameters carry
Behavior. The checker then verifies every
implementation that reaches either method. That annotation also
ties the surrogate to one Protocol, and that tie is what the
generic surrogate exists to avoid. test_state.py below passes
the same Surrogate a two-state stand-in that has a
name() and none of Behavior’s three
methods. With Behavior on those parameters,
ty rejects that test:
type StateA is not assignable to protocol Behavior.
Declaring the implementations as first: Behavior
and second: Behavior puts the check where it does
not restrict the surrogate. The type checker verifies that
Implementation1 and Implementation2
supply everything the Protocol declares, and reports a missing
method. That declaration covers the implementations, not the
surrogate.
The test passes the State surrogate a small stand-in and
confirms that calls reach the current implementation and that
change_to() swaps it:
# test_state.py
from state_surrogate import Surrogate
class StateA:
def name(self) -> str:
return "A"
class StateB:
def name(self) -> str:
return "B"
def test_state_delegates_and_change_swaps() -> None:
s = Surrogate(StateA())
assert s.name() == "A"
s.change_to(StateB())
assert s.name() == "B"Because GoF Design Patterns gives Proxy and
State different structures, it treats them as
unrelated. But both are a Surrogate: an object that
forwards method calls to an implementation. Proxy
controls access to a single implementation. State swaps
among several implementations to change behavior over time. Both
are the same few lines of __getattr__() delegation,
with State adding a method to change the
implementation. The separate implementation hierarchy in GoF
Design Patterns matters when other people write the
implementations and you need the base class to state which
methods an implementation must supply. When you write both
sides, the single generic surrogate in state_surrogate.py is
simpler and just as flexible.
CountingProxy in counting_proxy.py to keep
a per-method tally in a collections.Counter instead
of a single total. Confirm the tally reports f
called twice and g called once.counting_proxy.py,
misspell self._impl as self._imp
inside __getattr__() and run it. Use the
fallback-hook behavior this chapter describes to explain why the
failure reports as RecursionError rather than an
AttributeError naming the typo.dunder_bypass.py’s
Proxy cannot answer len(p). Give that
Proxy a __len__() that forwards to the
implementation, and confirm len(p) returns 2. Then
explain why __getattr__() could not have supplied
it.Surrogate in state_surrogate.py so
change_to() rejects an implementation missing a
method the current one has, and explain why the type checker
could not have reported that swap.