Question
This question is not for the discussion of whether or not the singleton design pattern is desirable, is an anti-pattern, or for any religious wars, but to discuss how this pattern is best implemented in Python in such a way that is most pythonic. In this instance I define 'most pythonic' to mean that it follows the 'principle of least astonishment'.
I have multiple classes which would become singletons (my use-case is for a logger, but this is not important). I do not wish to clutter several classes with added gumph when I can simply inherit or decorate.
Method 1: A decorator
- Singleton_1.py.py
- def singleton(clz):
- instances = {}
- def getinstance(*args, **kwargs):
- if clz not in instances:
- instances[clz] = clz(*args, **kwargs)
- return instances[clz]
- return getinstance
- @singleton
- class MyClass:
- def __init__(self, name, age):
- self.name = name
- self.age = age
- def __str__(self):
- return "{}/{}".format(self.name, self.age)
- print('type(MyClass)={}'.format(type(MyClass)))
- johnObj = MyClass('John', 37)
- print('johnObj={}'.format(johnObj))
- maryObj = MyClass('Mary', 25)
- print('maryObj={}'.format(maryObj))
- print('johnObj == maryObj? {}'.format(johnObj == maryObj))
* Pros: Decorators are additive in a way that is often more intuitive than multiple inheritance.
* Cons: While objects created using MyClass() would be true singleton objects, MyClass itself is a a function, not a class, so you cannot call class methods from it. Also for:
Method 2: A base class
- Singleton_2.py
- #!/usr/bin/env python
- class Singleton(object):
- _instance = None
- def __new__(clz, *args, **kwargs):
- if not isinstance(clz._instance, clz):
- clz._instance = object.__new__(clz, *args, **kwargs)
- print('New instance: {}'.format(*args))
- return clz._instance
- class MyClass(Singleton):
- def __init__(self, name, age):
- self.name = name
- self.age = age
- def __repr__(self):
- return "{}/{}".format(self.name, self.age)
- johnObj = MyClass('John', 37)
- maryObj = MyClass('Mary', 25)
- bobObj = MyClass('Bob', 41)
- print('johnObj == maryObj? {}'.format(johnObj == maryObj))
- print("{} (id={})".format(johnObj, id(johnObj)))
- print("{} (id={})".format(maryObj, id(maryObj)))
Pros: It's a true class
Cons: Multiple inheritance - eugh! __new__ could be overwritten during inheritance from a second base class? One has to think more than is necessary.
Method 3: A metaclass
- Singleton_3.py
- #!/usr/bin/env python
- 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]
- #Python2
- class MyClass:
- __metaclass__ = Singleton
- def __init__(self, name, age):
- self.name = name
- self.age = age
- def __str__(self):
- return "{}/{}".format(self.name, self.age)
- #Python3
- #class MyClass(metaclass=Singleton):
- # pass
- johnObj = MyClass('John', 37)
- maryObj = MyClass('Mary', 25)
- print("{} (id={})".format(johnObj, id(johnObj)))
- print("{} (id={})".format(maryObj, id(maryObj)))
* Pros
* Cons: Are there any?
Method 4: decorator returning a class with the same name
- def singleton(class_):
- class class_w(class_):
- _instance = None
- def __new__(class_, *args, **kwargs):
- if class_w._instance is None:
- class_w._instance = super(class_w,
- class_).__new__(class_,
- *args,
- **kwargs)
- class_w._instance._sealed = False
- return class_w._instance
- def __init__(self, *args, **kwargs):
- if self._sealed:
- return
- super(class_w, self).__init__(*args, **kwargs)
- self._sealed = True
- class_w.__name__ = class_.__name__
- return class_w
- @singleton
- class MyClass(BaseClass):
- pass
* Cons:
How-To
I would recommend Method #2, but you're better off using a metaclass than a base class. Here is a sample implementation:
- 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 Logger(object):
- __metaclass__ = Singleton
- # Python3
- #class Logger(metaclass=Singleton):
- # pass
- else:
- cls._instances[cls].__init__(*args, **kwargs)
A few words about metaclasses. A metaclass is the class of a class; that is, a class is an instance of its metaclass. You find the metaclass of an object in Python with type(obj). Normal new-style classes are of type type. Logger in the code above will be of type class 'your_module.Singleton', just as the (only) instance of Logger will be of type class 'your_module.Logger'. When you call logger with Logger(), Python first asks the metaclass of Logger, Singleton, what to do, allowing instance creation to be pre-empted. This process is the same as Python asking a class what to do by calling __getattr__ when you reference one of it's attributes by doing myclass.attribute.
A metaclass essentially decides what the definition of a class means and how to implement that definition. See for example http://code.activestate.com/recipes/498149/, which essentially recreates C-style structs in Python using metaclasses. The thread What are your (concrete) use-cases for metaclasses in Python? also provides some examples, they generally seem to be related to declarative programming, especially as used in ORMs.
In this situation, if you use your Method #2, and a subclass defines a __new__ method, it will be executed every time you call SubClassOfSingleton() -- because it is responsible for calling the method that returns the stored instance. With a metaclass, it will only be called once, when the only instance is created. You want to customize what it means to call the class, which is decided by it's type.
In general, it makes sense to use a metaclass to implement a singleton. A singleton is special because is created only once, and a metaclass is the way you customize the creation of a class. Using a metaclass gives you more control in case you need to customize the singleton class definitions in other ways.
Your singletons won't need multiple inheritance (because the metaclass is not a base class), but for subclasses of the created class that use multiple inheritance, you need to make sure the singleton class is the first / leftmost one with a metaclass that redefines __call__ This is very unlikely to be an issue. The instance dict is not in the instance's namespace so it won't accidentally overwrite it.
You will also hear that the singleton pattern violates the "Single Responsibility Principle" -- each class should do only one thing. That way you don't have to worry about messing up one thing the code does if you need to change another, because they are separate and encapsulated. The metaclass implementation passes this test. The metaclass is responsible for enforcing the pattern and the created class and subclasses need not be aware that they are singletons. Method #1 fails this test, as you noted with "MyClass itself is a a function, not a class, so you cannot call class methods from it."
Supplement
* FAQ - Why is __init__() always called after __new__()?
沒有留言:
張貼留言