multi-Singleton-like using __new__

Freek Dijkstra freek at macfreek.nl
Fri Feb 8 20:38:07 EST 2008


J Peyret wrote:

> >>> class RDFObject(object):
>
> ...     cache ={}
> ...     isInitialized = False
> ...     def __new__(cls,uri,*args,**kwds):
> ...         try:
> ...             return cls.cache[uri]
> ...         except KeyError:
> ...             print "cache miss"
> ...             res = cls.cache[uri] = object.__new__(cls)
> ...             return res
> ...     def __init__(self,uri):
> ...         if self.isInitialized:
> ...             return
> ...         print "__init__ for uri:%s" % (uri)
> ...         self.isInitialized = True
> ...>>> r1 = RDFObject(1)

Thanks. I first got that option, but discarded it because of the
following (sloppy) coding, which  still leads to double
initializations:

class MySubclass(RDFObject):
    def __init__(self, uri):
        self.somevar = []
        RDFObject.__init__(self, uri)
        # ....

In this case, somevar is re-initialized before the return statement in
the parent.

I'll use __metaclass__ solution. Here is my (non-threaded) version:

class RDFObject(object):
    _cache = {}   # class variable is shared among all RDFObject
instances
    class __metaclass__(type):
        def __call__(cls, *args, **kwargs):
            return cls.__new__(cls, *args, **kwargs)
    def __new__(cls, uri, *args, **kargs):
        if uri not in cls._cache:
            obj = object.__new__(cls)
            cls._cache[uri] = obj
            obj.__init__(uri, *args, **kargs)
        return cls._cache[uri]
    def __init__(self, uri):
        self.uri = uri
        print self.__class__, uri
    # ...

Thanks for the other 'things to keep in mind'. I found those all very
informative!

Two more things for all lurkers out there:

- If you override __call__ in __metaclass__ to only call __new__, but
skip on __init__, you must make sure to call __init__ elsewhere. For
example, from __new__:
        if uri not in cls._cache:
            obj = object.__new__(cls)
            cls._cache[uri] = obj
            obj.__init__(uri, *args, **kargs)

- My "thread safe" code was in fact NOT thread safe. It still can
create the same object twice in the (very unlikely event) that thread
#2 executes "cls._cache[uri] = obj" just between the lines "if uri not
in cls._cache:" and "threadlock.acquire()" in thread #1. (bonus points
for those who caught this one).

Perhaps indeed the try...except KeyError is even prettier (no idea
about speed, but let's rename this thread if we want to discuss
performance measurements).

Thanks all!
Freek



More information about the Python-list mailing list