Monday, February 2, 2015

Python Singleton pattern - generic approach. Part II

This is the second part that talks about Python generic singleton pattern. In the first part I've talked about decorator approach and now we'll look into classmethod approach.

@classmethod approach

Another way to implement singleton is to prevent any actual instance creation and use our class itself as a singleton:
class SingletonClass(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls

class MyClass(SingletonClass):
   @classmethod
   def __init__(cls, x, y):
       print "init is here"
            
   @classmethod
   def do(cls):
       print "doing stuff"

The downside is that we now have to decorate each of our methods with @classmethod which is inconvenient and defies my original desire that I could turn any existing class into singleton.

OK, so if we need to type @classmethod string before each and every method we define in MyClass, why don't we kindly ask Python do it for us? Metaclasses comes to mind - we actually want to convert all methods of the MyClass to classmethods. MyClass is created during its type __new__ stage, so lets hook in:

class SingletonClassMeta(type):
    def __new__(mcls, *args, **kwargs):
        cls = super(SingletonClassMeta, mcls).__new__(mcls, *args, **kwargs)
        for k, v in cls.__dict__.items():
            if isinstance(v, types.FunctionType):
                setattr(cls, k, classmethod(v))
        return cls

class SingletonClass(object):
    __metaclass__ = SingletonClassMeta
    __initialized = False
    ...

We just check that if MyClass has an attribute that is plain function, we convert this function to classmethod (decorators are just callables, so we are calling them :). Now the definition of MyClass is more elegant:

class MyClass(SingletonClass):
   def __init__(cls, x, y):
       print "init is here"
            
   def do(cls):
       print "doing stuff"

This is almost perfect. I write my class as usual, just need to inherit from SingletonClass. I say "almost", because given third-party class OtherClass I still can not convert it to singleton. The below will NOT make MyClass.do to behave as if MyClass was a singleton:

class OtherClass(object):
   def __init__(cls, x, y):
       print "init is here"
            
   def do(cls):
       print "doing stuff"

class MyClass(SingletonClass, OtherClass): pass

It does not do the trick because the metaclass's __new__ augments attributes defined in our class (MyClass that is) and in the later case, MyClass does not define any attributes at all - they all are in its base class OtherClass.

So how to solve this? Well, we could look through our MRO chain during metacalss __new__ and to augment attributes of our base classes, but that's quite a destructive process - we screw things up for other regular users of a particular base class.

To overcome this we need to hook in into attribute lookup mechanism of MyClass. Here is the revised metaclass:

import types
class SingletonClassMeta(type):
    def __getattribute__(cls, attr):
        val = super(SingletonClassMeta, cls).__getattribute__(attr)
        if isinstance(val, types.MethodType) and not val.__self__:
            val = types.MethodType(val.__func__, cls, type(cls))
            # the above is equivalent to:
            # val = val.__func__.__get__(cls, type(cls))
        return val

Here our metaclass overloads the attribute lookup machinery:

  • We are doing regular attribute lookup
  • Then we check that attribute value is an unbound method - by checking that it is a method and its self is None
  • If its an unbound methond, we construct new, bound method on the fly from the original function

There is one rather long but important point to note here:

  • Previously, code in __new__ method change attributes of the class before they were ever accessed. Attribute retrieval machinery is described here, but in very short it can be summed as "Look the __dict of instance, its class and its parents. If whatever you found has __get__ method, call it with current instance and its type and return result to the caller".
  • Every python function is a descriptor! That is, when you stuff any function in some class __dict__ and then try to retrieve it later as an attribute, descriptor protocol kicks in and returns bound/unbound methods instead.
  • Back to __new__ - when we set class attribute, it needs to be something that would play nice with attribute lookup protocol. We used classmethod which is a decorator object that remembers a function and then acts as descriptor converting remembered function to bound (to class) method when it get fetched. We could (almost) equally just do setattr(cls, k, functools.partial(v, cls)) - it would do the job as well, but would break semantics, because if someone would inspect our class methods it would see <functools.partial object... instead of <bound method..., that's why I prefer to use classmethod
  • In our last example, we took full responsibility for object retrieval. Thus if we try to just return classmethod(val.__func__) we'd fail with
    TypeError: 'classmethod' object is not callable
    
    We took the responsibility, so no one cares if we are returning a descriptor. That is, we need to build and return bound method by ourselves. Again, every function is a descriptor, i.e. every function has __get__ method. You can construct method from any function - it does not even have to belong to any particular class:
    >>> class C(object): pass
    ... 
    >>> def a(): pass
    ... 
    >>> a.__get__(C(), C) # instance method of C()
    >
    >>> 
    >>> a.__get__(C, type(C)) # class method of C
    >
    >>> 
    
    

No comments:

Post a Comment