ActiveState Code

Recipe 102187: Singleton as a metaclass


Use of metaclasses to hide access to class constructor.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"""
USAGE:
class A:
	__metaclass__ = Singleton
	def __init__(self):
		self.a=1
		
a=A()
b=A()
a is b #true

You don't have access to the constructor, 
you only can call a factory that returns always the same instance.
"""

_global_dict = {}

def Singleton(name, bases, namespace):
	class Result:pass
	Result.__name__ = name
	Result.__bases__ = bases
	Result.__dict__ = namespace
	_global_dict[Result] = Result()
	return Factory(Result)


class Factory:
	def __init__(self, key):
		self._key = key
	def __call__(self):
		return _global_dict[self._key]
		
def test():
	class A:
		__metaclass__ = Singleton
		def __init__(self):
			self.a=1	
	a=A()
	a1=A()
	print "a is a1", a is a1
	a.a=12
	a2=A()
	print "a.a == a2.a == 12", a.a == a2.a == 12
	class B:
		__metaclass__ = Singleton
	b=B()
	a=A()
	print "a is b",a==b

Discussion

Metaclasses are a powerfull tool to change behaviour to classes. To use metaclasses you must provide a callable object that accepts 3 arguments: name, a tuple of base classes and a namespace. class Dummy:pass def x(*args): print args return Dummy

class A(B): a=1 __metaclass__ = x

When the code is compiled x is called and prints: ('A',(B,),{'a':1}) if you do:

A

The big problem is that a class A can't be subclassed. If you want to subclass: class _A: def a(self):return "a" class A(_A): ___metaclass__=Singleton class _B(_A): def b(self):return "b" class B(_B): __metaclass__ = Singleton

Comments

  1. 1. At 9:49 a.m. on 24 jan 2002, Steve Alexander said:

    This simplifies things and allows subclassing.

    _global_dict = {}
    
    class Singleton:
        def __init__(self, name, bases, namespace):
            ns=[item for item in namespace.iteritems()]
            ns.sort()
            key=(name,bases,tuple(ns))
            if key in _global_dict:
                self._obj = _global_dict[key]
            else:
                self._obj = _global_dict[key] = type(name,bases,namespace)
    
        def __call__(self):
            return self._obj
    
  2. 2. At 9:58 a.m. on 24 jan 2002, Steve Alexander said:

    Even better: this is probably as simple as it gets...

    class Singleton:
        def __init__(self, name, bases, namespace):
            self._obj=type(name,bases,namespace)
    
        def __call__(self):
            return self._obj
    
  3. 3. At 10:12 a.m. on 24 jan 2002, Steve Alexander said:

    ...and it doesn't work. The previous examples give the correct test output, but don't function as actual classes. Doh!

    I'll have a think about this and post back once I've tested it properly.

    Sorry for the noise!

  4. 4. At 10:24 a.m. on 24 jan 2002, Steve Alexander said:

    This one works! Silly really... in the last example, I was returning a class rather than an object from my factory.

    class Singleton:
        def __init__(self, name, bases, namespace):
            self._obj=type(name,bases,namespace)()
    
        def __call__(self):
            return self._obj
    
  5. 5. At 7:45 a.m. on 6 feb 2002, Ivan Vigasin said:

    Class variables and __metaclass__.

    What's wrong with the following code?
    >>> class A:
    ...   __metaclass__ = Singleton
    ...   var = 1
    ...   def __init__(self):
    ...     A.var = A.var + 1
    ...
    Traceback (most recent call last):
      File "", line 1, in ?
      File "", line 3, in __init__
      File "", line 5, in __init__
    NameError: global name 'A' is not defined
    >>> class A:
    ...   var = 1
    ...   def __init__(self):
    ...     A.var = A.var + 1
    ...
    >>>
    
  6. 6. At 7:56 a.m. on 7 mar 2002, Steve Alexander said:

    Here's a version that copes with your code. The problem is that the Singleton is trying to construct an instance of your class before the class has finished being fully defined. Hence, you don't get access to a bound variable for your class.

    Here's a version that gets around that problem, although it adds a line or two more of code:

    class Singleton(type):
    
        _obj = None
    
        def __init__(self, name, bases, namespace):
            type.__init__(self,name,bases,namespace)
    
        def __call__(self):
            if self._obj is None:
                self._obj = type.__call__(self)
            return self._obj
    
  7. 7. At 7:14 p.m. on 21 may 2003, Tim Keating said:

    One small recommended change. Use 'super' to make your metaclass safe for multiple inheritance:

    class Singleton(type):
    
        _obj = None
    
        def __init__(self, name, bases, namespace):
            super(Singleton, self).__init__(name,bases,namespace)
    
        def __call__(self):
            if self._obj is None:
                self._obj = type.__call__(self)
            return self._obj
    
  8. 8. At 5:43 a.m. on 21 jul 2003, Michele Simionato said:

    This is not inheritance safe. If you do

    class C:
        __metaclass__=Singleton
    
    class D(C):
        pass
    
    c=C()
    d=D()
    
    print d
    
    you will see that d is None.
    
    Here is a Singleton metaclass that works:
    
    class Singleton(type):
        def __init__(cls,name,bases,dic):
            super(Singleton,cls).__init__(name,bases,dic)
            cls.instance=None
        def __call__(cls,*args,**kw):
            if cls.instance is None:
                cls.instance=super(Singleton,cls).__call__(*args,**kw)
            return cls.instance
    

    Michele

Sign in to comment