@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__dictof 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 usedclassmethodwhich 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 dosetattr(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 useclassmethod - In our last example, we took full responsibility for object retrieval. Thus if we try to just return
classmethod(val.__func__)we'd fail withTypeError: '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