2.7 KiB
Singleton class decorator
A singleton is a class which has only one instance. IT gurus debate whether they should be used, mainly on the grounds that project aims can change: a need for multiple instances may arise later. I would argue that singleton classes have merit in defining interfaces to hardware objects. You can be sure that (for example) a Pyboard D will only have one RTC instance.
The advantage of a singleton is that it removes the need for a global instance or for passing an instance between functions. The sole instance is retrieved at any point in the code using constructor call syntax.
def singleton(cls):
instance = None
def getinstance(*args, **kwargs):
nonlocal instance
if instance is None:
instance = cls(*args, **kwargs)
return instance
return getinstance
@singleton
class MySingleton:
def __init__(self, arg):
self.state = arg
print('In __init__', arg)
def foo(self, arg):
print('In foo', arg + self.state)
ms = MySingleton(42) # prints 'In __init__ 42'
x = MySingleton() # No output: assign existing instance to x
x.foo(5) # prints 'In foo 47': original state + 5
The first instantiation sets the object's initial state. Thereafter 'instantiations' retrieve the original object.
There are other ways of achieving singletons. One is to define a (notionally
private) class in a module. The module API contains an access function. There
is a private instance, initially None
. The function checks if the instance is
None
. If so it instantiates the object and assigns it to the instance. In all
cases it returns the instance.
Both have similar logic. The decorator avoids the need for a separate module.
Functor class decorator
The term "functor" derives from "function constructor". It is a function-like object which can retain state. Like singletons the aim is to avoid globals: the state is contained in the functor instance.
def functor(cls):
instance = None
def getinstance(*args, **kwargs):
nonlocal instance
if instance is None:
instance = cls(*args, **kwargs)
return instance
return instance(*args, **kwargs)
return getinstance
@functor
class MyFunctor:
def __init__(self, arg):
self.state = arg
print('In __init__', arg)
def __call__(self, arg):
print('In __call__', arg + self.state)
MyFunctor(42) # prints 'In __init__ 42'
MyFunctor(5) # 'In __call__ 47'
A use case is in asynchronous programming. The constructor launches a task: this will only occur once. Subsequent calls might alter the behaviour of that task. An example may be found in the Latency class.