Reputation: 63619
This might be a silly question since I just learnt about Metaclasses....
If you have 2 Singleton classes each in their own file, is it poor practice to duplicate the Singleton
metaclass definition in both files, like
Apple.py
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Apple(metaclass=Singleton):
pass
print(Apple() == Apple()) # True
Orange.py
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Orange(metaclass=Singleton):
pass
print(Orange() == Orange()) # True
Or should we move the Singleton metaclass code into its own file Singleton.py
, and import them into Apple.py
and Orange.py
?
Or use a @singleton
decorator from the package singleton-decorator
?
Upvotes: 0
Views: 2071
Reputation: 110301
Actually, it is a silly practice to use metaclasses to define singletons. I don't know who originally came up with this pattern, it is now widespread, and it is way overkill.
That said, just for a quick answer: of course it does not make sense to duplicate code - much less so when you won't be changing even a single parameter -it is the exact same code. Just keep it in a file and import it from the other, even if you don't want to create an specific file just for that.
Now, metaclasses are a powerful feature in Python that is best used when one knows what is going on. Otherwise, there are side effects, the most common of which is being unable to combine your classes with others that have a metaclass (which might actually be a necessary one).
That said, this is a non-metaclass singleton pattern based on decorators:
_register = {}
def singleton(cls):
def wrapper(*args, **kw):
if cls not in _register:
instance = cls(*args, **kw)
_register[cls] = instance
return _register[cls]
wrapper.__name__ = cls.__name__
return wrapper
...
@singleton
class Orange:
...
The code above is provided as is, but if using it beware it does not warn about someone trying to create a second instance with different parameters. It could be changed to allow no parameters, or to raise an error on re-instantiation.
Better yet, the decorator pattern can create the singleton instance right on class declaration, unlike what goes with metaclasses.Then, there is not even the need for a register, since the class won't be available for one to try to produce an incorrect second-instance of it (although one could do type(Orange)()
- if you are in a project which is a context for who can break the code:
def singleton(cls):
return cls()
That is it.
@singleton
class Orange:
...
after executed, this will add "Orange" to the namespace as the one and singly allowed instance for the "Orange" class as declared. Or for that matter, just do:
class Orange:
...
Orange = Orange()
(and voilá - you even resolved your problem of sharing code across modules - there is no extra code needed at all)
Upvotes: 2