attrs
attrs copied to clipboard
Is there a canonical way to create a singleton with attrs?
I've got a few cases where I require a singleton object in my code and was wondering if there's an accepted way to create a singleton with attrs support for initialization and setattr.
#579 makes it reasonably clear that there won't be builtin support for singletons, but the pattern is still useful when you need a per-process unique instance of a class. That thread mentions a few alternatives, using the class directly and using a module. The latter option doesn't let me use dunders like __getitem__ or __class_getitem__ and neither option lets me use attrs' existing infrastructure for initialization, validation, or freezing.
Right now I add the following to an attrs class to make a singleton:
@define
class Config():
def __new__(cls):
"""
Creates a singleton object, if it is not created,
or else returns the previous singleton object
"""
if not hasattr(cls, "instance"):
instance = super(Config, cls).__new__(cls)
instance.__init__()
instance.__attrs_init__()
cls.instance = instance
return cls.instance
def __init__(self):
pass
This works well enough for my case, but I'm wondering if there's a better way to accomplish the same thing? Either an actual implementation for a singleton using attrs or an alternate design pattern that achieves the same goal.
In attrs we have exactly one singleton and that's _Nothing:
https://github.com/python-attrs/attrs/blob/4252adbab3aa05fe14b3112f31fbfc2edaa41e51/src/attr/_make.py#L56-L76
If you find a better way, pls let us know. :)
In my own code, I tend to enforce such policies using global variables + functions which is less magicy:
_config = None
def get_config():
global _config
if _config is None:
_config = Config()
return _config
There's so many variables here that can affect how you'd implement this. Does it take zero arguments, and thus it's just a fancy sentinel object? Does it take arguments, and should use a cached instance based on what the arguments are? Or does it just ignore the arguments from the second time? Does it need to be made into a singleton lazily, or as soon as the module is imported?
It seems like it's outside the scope of attrs. The docs recommend a builder pattern over hooking in to initialization-- maybe they should recommend doing singleton logic outside of the class, and merely hiding the class itself? Or just not exposing the class outside of the module?
But also, if your singleton doesn't take any parameters-- what do you need attrs for?
(By the way: if your example is close to what you want, and you need something reusable...)
def singleton(class_):
instance = None
@functools.wraps(class_)
def _wrapped():
nonlocal instance
if instance is None:
instance = class_()
return instance
return _wrapped