@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 usedclassmethod
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 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