2018年6月2日 星期六

[ Python 常見問題 ] Creating a singleton in Python

Source From Here 
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 
  1. def singleton(clz):  
  2.     instances = {}  
  3.     def getinstance(*args, **kwargs):  
  4.         if clz not in instances:  
  5.             instances[clz] = clz(*args, **kwargs)  
  6.   
  7.         return instances[clz]  
  8.   
  9.     return getinstance  
  10.   
  11.   
  12. @singleton  
  13. class MyClass:  
  14.     def __init__(self, name, age):  
  15.         self.name = name  
  16.         self.age = age  
  17.   
  18.     def __str__(self):  
  19.         return "{}/{}".format(self.name, self.age)  
  20.   
  21.   
  22. print('type(MyClass)={}'.format(type(MyClass)))  
  23. johnObj = MyClass('John'37)  
  24. print('johnObj={}'.format(johnObj))  
  25. maryObj = MyClass('Mary'25)  
  26. print('maryObj={}'.format(maryObj))  
  27. print('johnObj == maryObj? {}'.format(johnObj == maryObj))  
Execution output: 
# ./Singleton_1.py
type(MyClass)=
johnObj=John/37
maryObj=John/37
johnObj == maryObj? True

* 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: 
>>> m = MyClass('John', 38)
>>> n = MyClass('Mary', 25)
>>> m == n // m and n are same object!
True
>>> n

>>> o = n.__class__('Peter', 17)
>>> print(m)
John/37
>>> print(n)
John/37
>>> print(o)
Peter/17
>>> o == m
False


Method 2: A base class 
Singleton_2.py 
  1. #!/usr/bin/env python  
  2. class Singleton(object):  
  3.     _instance = None  
  4.     def __new__(clz, *args, **kwargs):  
  5.         if not isinstance(clz._instance, clz):  
  6.             clz._instance = object.__new__(clz, *args, **kwargs)  
  7.             print('New instance: {}'.format(*args))  
  8.   
  9.         return clz._instance  
  10.   
  11.   
  12. class MyClass(Singleton):  
  13.     def __init__(self, name, age):  
  14.         self.name = name  
  15.         self.age = age  
  16.   
  17.     def __repr__(self):  
  18.         return "{}/{}".format(self.name, self.age)  
  19.   
  20.   
  21. johnObj = MyClass('John'37)  
  22. maryObj = MyClass('Mary'25)  
  23. bobObj = MyClass('Bob'41)  
  24. print('johnObj == maryObj? {}'.format(johnObj == maryObj))  
  25. print("{} (id={})".format(johnObj, id(johnObj)))  
  26. print("{} (id={})".format(maryObj, id(maryObj)))  
Execution output: 
New instance: John
johnObj == maryObj? True
Bob/41 (id=139778152652240)
Bob/41 (id=139778152652240)
 # Check the strange behavior here and think about whey

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 
  1. #!/usr/bin/env python  
  2. class Singleton(type):  
  3.     _instances = {}  
  4.     def __call__(cls, *args, **kwargs):  
  5.         if cls not in cls._instances:  
  6.             cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)  
  7.         return cls._instances[cls]  
  8.   
  9. #Python2  
  10. class MyClass:  
  11.     __metaclass__ = Singleton  
  12.   
  13.     def __init__(self, name, age):  
  14.         self.name = name  
  15.         self.age = age  
  16.   
  17.     def __str__(self):  
  18.         return "{}/{}".format(self.name, self.age)  
  19.   
  20. #Python3  
  21. #class MyClass(metaclass=Singleton):  
  22. #    pass  
  23.   
  24.   
  25. johnObj = MyClass('John'37)  
  26. maryObj = MyClass('Mary'25)  
  27. print("{} (id={})".format(johnObj, id(johnObj)))  
  28. print("{} (id={})".format(maryObj, id(maryObj)))  
Execution output: 
# ./Singleton_3.py
John/37 (id=140135502437776)
John/37 (id=140135502437776)

* Pros 
It's a true class
Auto-magically covers inheritance
Uses __metaclass__ for its proper purpose (and made me aware of it)

* Cons: Are there any? 

Method 4: decorator returning a class with the same name 
  1. def singleton(class_):  
  2.     class class_w(class_):  
  3.         _instance = None  
  4.         def __new__(class_, *args, **kwargs):  
  5.             if class_w._instance is None:  
  6.                 class_w._instance = super(class_w,  
  7.                                     class_).__new__(class_,  
  8.                                                     *args,  
  9.                                                     **kwargs)  
  10.                 class_w._instance._sealed = False  
  11.             return class_w._instance  
  12.         def __init__(self, *args, **kwargs):  
  13.             if self._sealed:  
  14.                 return  
  15.             super(class_w, self).__init__(*args, **kwargs)  
  16.             self._sealed = True  
  17.     class_w.__name__ = class_.__name__  
  18.     return class_w  
  19.   
  20. @singleton  
  21. class MyClass(BaseClass):  
  22.     pass  
* Pros: It's a true class and auto-magically covers inheritance 
* Cons: 
Is there not an overhead for creating each new class? Here we are creating two classes for each class we wish to make a singleton. While this is fine in my case, I worry that this might not scale. Of course there is a matter of debate as to whether it aught to be too easy to scale this pattern...
What is the point of the _sealed attribute?
Can't call methods of the same name on base classes using super() because they will be recursive. This means you can't customize __new__ and can't subclass a class that needs you to call up to __init__.


How-To 
I would recommend Method #2, but you're better off using a metaclass than a base class. Here is a sample implementation: 
  1. class Singleton(type):  
  2.     _instances = {}  
  3.     def __call__(cls, *args, **kwargs):  
  4.         if cls not in cls._instances:  
  5.             cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)  
  6.         return cls._instances[cls]  
  7.   
  8. class Logger(object):  
  9.     __metaclass__ = Singleton  
  10.   
  11. # Python3  
  12. #class Logger(metaclass=Singleton):  
  13. #   pass  
If you want to run __init__ every time the class is called, add: 
  1. else:  
  2.     cls._instances[cls].__init__(*args, **kwargs)  
to the if statement in Singleton.__call__

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 typeLogger 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__()? 

沒有留言:

張貼留言

[ Python 文章收集 ] List Comprehensions and Generator Expressions

Source From  Here   Preface   Do you know the difference between the following syntax?  view plain copy to clipboard print ? [x  for ...