Notes and exercises for learning design patterns
The decorator receives a class:
def singleton(cls):
...
Then it returns a wrapper function:
def get_instance(*args, **kwargs):
...
The wrapper owns a closure variable:
instance = None
The first call creates the object. Later calls return the cached object.
After decoration, this:
@singleton
class MetricsRegistry:
...
roughly becomes this:
MetricsRegistry = singleton(MetricsRegistry)
So calling MetricsRegistry(...) now calls get_instance(...), not the original class directly.
reset_instance() is includedThe cached object lives in a closure, so it is not directly visible as MetricsRegistry._instance.
That is why the decorator exposes:
MetricsRegistry.reset_instance()
This is useful in exercises and tests because it lets you create a fresh singleton object for the next scenario.
The simple decorator version changes what the class name means.
Before decoration, MetricsRegistry is a class. After decoration, MetricsRegistry is a function that returns the singleton instance.
That can make isinstance(obj, MetricsRegistry) awkward because MetricsRegistry no longer points to the class. The solution stores the original class on __wrapped__, but the public name is still the wrapper function.
This is the main reason to learn the metaclass version next.