singleton objects with decorators

Bengt Richter bokr at oz.net
Mon Apr 11 23:42:06 EDT 2005


On Mon, 11 Apr 2005 17:26:09 +0200, Uwe Mayer <merkosh at hadiko.de> wrote:

>Hi,
>
>I've been looking into ways of creating singleton objects. With Python2.3 I
>usually used a module-level variable and a factory function to implement
>singleton objects.
>
>With Python2.4 I was looking into decorators. The examples from PEP 318
>http://www.python.org/peps/pep-0318.html#examples
>
>don't work - AFAIK because:
>- you cannot decorate class definitions (why was it left out?)
>- __init__ must return None
>
>
>However, you can use the decorator:
>
>def singleton(f):
>    instances = {}
>    def new_f(*args, **kwargs):
>        if (f not in instances):
>            instances[f] = f(*args, **kwargs)
>        return instances[f]
>    new_f.func_name = f.func_name
>    new_f.func_doc = f.func_doc       
>    return new_f
>
>with a class that overwrites the __new__ methof of new-style classes:
>
>class Foobar(object):
>    def __init__(self):
>        print self
>
>    @singleton
>    def __new__(self):
>        return object.__new__(Foobar)
>
>
>Is this particularly ugly or bad?
>
>Thanks for comments,

I thought of a different approach. The singleton function here
takes arguments for the initial instance of a class, and instantiates it,
and then modifies the class's __new__ and __init__ to return the initial
instance and prevent further initialization.

Used directly, as normal function decorators can be (i.e., fun = deco(fun)
or fun = deco(args)(fun)), singleton works (not counting bugs ;-) as is.

To invoke it as decorator demands a trick workaround using an intermediate
workaround decorator and a dummy funcion definition synonymous with the
class to be decorated (which mus pre-exist for this).

BTW, this is different from the class-decorator-as-sugar-for-metaclass,
(which I suggested previously) since it take one class argument vs arguments
for the metaclass __new__ etc.


I added a singularize decorator for when you don't immediately want to
specify the first initiazization parameters, but want the first instance
to to become the singleton instance whenever that happens later.

So here's some hacks to play with:

----< singleton.py >-----------------------------------------------
def singleton(*args, **kw):
    """decorator cacheing singleton instance immediately"""
    def singdeco(cls):
        assert isinstance(cls, type)
        inst = cls(*args, **kw)
        cls.__new__ = staticmethod(lambda *a, **kw: inst)
        cls.__init__ = staticmethod(lambda *a, **kw: None) # no re-init
        return cls
    return singdeco

import sys
def workaround(f):
    cls = sys._getframe(1).f_locals.get(f.func_name)
    if cls is None:
        cls = sys._getframe(1).f_globals.get(f.func_name)
    return cls

def singularize(cls):
    """decorator setting singleton-making mousetrap"""
    oldnew = cls.__new__
    def __new__(cls, *args, **kw):
        inst = oldnew(cls)
        inst.__init__(*args, **kw)
        cls.__new__ = staticmethod(lambda *a,**kw: inst)
        cls.__init__ = staticmethod(lambda *a, **kw: None)
        return inst
    cls.__new__ = staticmethod(__new__)
    return cls

def test():
    class Foobar(object): pass
    Foobar = singleton()(Foobar)
    print Foobar
    print [Foobar() for x in xrange(2)]

    class Boobar(object):
        def __init__(self, v):
            print self, v
            self.v = v
    @singleton(123)
    @workaround
    def Boobar(): pass  # dummy used by workaround for name
    
    print Boobar
    print [Boobar(x) for x in xrange(2)]
    print [Boobar(x).v for x in xrange(2)]
    
    class Gee(object):
        def __init__(self): print 'Gee:', self

    Gee = singularize(Gee)
    print Gee
    print [Gee() for x in xrange(2)]

    class Haw(object):
        def __init__(self, *args, **kw):
            self.args = args
            self.kw =kw

    Haw = singularize(Haw)
    haw = Haw(1,2L, 3.5, s='string', more='example')
    print vars(haw)
    print vars(Haw(111, a='222'))
    print [Haw(), Haw()]
    print [Haw().args[i] for i in xrange(len(Haw().args))]
    print [Haw().kw['s'], Haw().kw['more']]

    
if __name__ == '__main__':
    test()
-------------------------------------------------------------------
No guarantees, mind ;-)

Output, showing single instances and no repeat initialization:

<class '__main__.Foobar'>
[<__main__.Foobar object at 0x02F03BEC>, <__main__.Foobar object at 0x02F03BEC>]
<__main__.Boobar object at 0x02F03DCC> 123
<class '__main__.Boobar'>
[<__main__.Boobar object at 0x02F03DCC>, <__main__.Boobar object at 0x02F03DCC>]
[123, 123]
<class '__main__.Gee'>
Gee: <__main__.Gee object at 0x02F03E8C>
[<__main__.Gee object at 0x02F03E8C>, <__main__.Gee object at 0x02F03E8C>]
{'args': (1, 2L, 3.5), 'kw': {'s': 'string', 'more': 'example'}}
{'args': (1, 2L, 3.5), 'kw': {'s': 'string', 'more': 'example'}}
[<__main__.Haw object at 0x02F03F0C>, <__main__.Haw object at 0x02F03F0C>]
[1, 2L, 3.5]
['string', 'example']

Regards,
Bengt Richter



More information about the Python-list mailing list