patternpythonMinor
Singleton base class
Viewed 0 times
singletonclassbase
Problem
I just wrote a singleton base class for a hobby project I'm currently working on. Python is not my first language, which is why I am not sure if everything is done the pythonic way.
I also used multiple resources/tutorials/articles for Python2 and 3. My singleton is supposed to be Python3 compatible. The implementation should be thread safe, which is why I enter a lock while creating the instance.
To allow custom initializations I decided to let the user provide a
Please tell me what can I do better, more efficient, and more pythonic.
```
#!/usr/bin/env python3
# -- coding: utf-8 --
"""
this module implements provides a baseclass for
a singleton.
it allows you to simply create singleton objects
by overriding the PyRlSingleton class.
it gives you access to the singleton instance in
a explicit way via a "get_instance()" function.
if you mistakingly try to create your singleton
class it raises an exception telling you that this
is not allowed.
you have to provide a "create_instance()" function
which creates your singleton implementation; this
is automatically called when the singleton is used
the first time.
the creating process is thread safe, so you can use
it in a multithreading environment.
see the "main()" function for a simple example.
have fun!
"""
import _thread # allocate_lock()
class PyRlSingletonMeta(type):
def __call__(cls, *args, **kwargs):
"""
this method disallows creation of a
singleton via a ClassName() call
"""
raise Exception("not allowed, use get_instance()")
class PyRlSingleton(object, metaclass = PyRlSingletonMeta):
__lock = _thread.allocate_lock()
__inst = None
@classmethod
def create_instance(cls):
"""
this is the default create_instance me
I also used multiple resources/tutorials/articles for Python2 and 3. My singleton is supposed to be Python3 compatible. The implementation should be thread safe, which is why I enter a lock while creating the instance.
To allow custom initializations I decided to let the user provide a
create_class function where the object could be created and initialized. The base class is more like a "singleton-factory" than a "classical" singleton where the object itself provides both (logic and singleton).Please tell me what can I do better, more efficient, and more pythonic.
```
#!/usr/bin/env python3
# -- coding: utf-8 --
"""
this module implements provides a baseclass for
a singleton.
it allows you to simply create singleton objects
by overriding the PyRlSingleton class.
it gives you access to the singleton instance in
a explicit way via a "get_instance()" function.
if you mistakingly try to create your singleton
class it raises an exception telling you that this
is not allowed.
you have to provide a "create_instance()" function
which creates your singleton implementation; this
is automatically called when the singleton is used
the first time.
the creating process is thread safe, so you can use
it in a multithreading environment.
see the "main()" function for a simple example.
have fun!
"""
import _thread # allocate_lock()
class PyRlSingletonMeta(type):
def __call__(cls, *args, **kwargs):
"""
this method disallows creation of a
singleton via a ClassName() call
"""
raise Exception("not allowed, use get_instance()")
class PyRlSingleton(object, metaclass = PyRlSingletonMeta):
__lock = _thread.allocate_lock()
__inst = None
@classmethod
def create_instance(cls):
"""
this is the default create_instance me
Solution
This line isn't needed in Python 3:
so you should probably just remove it. UTF-8 is assumed.
Your docstring is indented - why? It's not technically wrong, but it seem like a really arbitrary choice.
I'm not sure how to say this, but none of your sentences are capitalized. If this were a professional code-base, I would absolutely require good grammar and care of documentation. It is, after all, the primary text that represents the code.
I see
Since you're using Python 3, don't inherit from
You have
This is as simple to use as
and doesn't even affect the class' attributes, due to the magic of closures. One could use a class decorator instead, but I think this looks nicest. Note that this also allows usage on
Past that, I advise you to stick to PEP 8 where you can, which primarily means minor changes for your code.
On a design level, I strongly suggest rethinking using singletons at all. Globals are almost always better in such cases, and for the case of logging singletons aren't apropriate anyway (look instead at neat things like Logbook).
Even if you do conclude, for some reason, that you want singletons, at least consider the Borg pattern instead, which is mildly less aggrivating.
# -*- coding: utf-8 -*-so you should probably just remove it. UTF-8 is assumed.
Your docstring is indented - why? It's not technically wrong, but it seem like a really arbitrary choice.
I'm not sure how to say this, but none of your sentences are capitalized. If this were a professional code-base, I would absolutely require good grammar and care of documentation. It is, after all, the primary text that represents the code.
I see
_thread - you should be using the main threading interface. In particular, use threading.Lock instead of _thread.allocate_lock.Since you're using Python 3, don't inherit from
object - it's a no-op.You have
Logger.get_instance() return an instance of a different class. By this point, one should probably just make a get_logger function instead. Since that's not ideal, perhaps just overload __new__. You can do this with a metaclass, but a decorator is much simpler:import threading
from functools import wraps
def singleton_new(make_instance):
lock = threading.Lock()
instance = None
@wraps(make_instance)
def __new__(cls):
nonlocal instance
if instance is None:
with lock:
if instance is None:
instance = make_instance(cls)
return instance
return __new__This is as simple to use as
class Logger:
@singleton_new
def __new__(cls):
return super().__new__(cls)
def log(self, text):
print(text)and doesn't even affect the class' attributes, due to the magic of closures. One could use a class decorator instead, but I think this looks nicest. Note that this also allows usage on
classmethods like@classmethod
@singleton_new
def get_instance(cls):
return super().__new__(cls)Past that, I advise you to stick to PEP 8 where you can, which primarily means minor changes for your code.
On a design level, I strongly suggest rethinking using singletons at all. Globals are almost always better in such cases, and for the case of logging singletons aren't apropriate anyway (look instead at neat things like Logbook).
Even if you do conclude, for some reason, that you want singletons, at least consider the Borg pattern instead, which is mildly less aggrivating.
Code Snippets
# -*- coding: utf-8 -*-import threading
from functools import wraps
def singleton_new(make_instance):
lock = threading.Lock()
instance = None
@wraps(make_instance)
def __new__(cls):
nonlocal instance
if instance is None:
with lock:
if instance is None:
instance = make_instance(cls)
return instance
return __new__class Logger:
@singleton_new
def __new__(cls):
return super().__new__(cls)
def log(self, text):
print(text)@classmethod
@singleton_new
def get_instance(cls):
return super().__new__(cls)Context
StackExchange Code Review Q#93539, answer score: 5
Revisions (0)
No revisions yet.