Singleton/Borg pattern based on different parameters passed while creating the object
Question: I am using borg pattern to share state amongst the objects:

    
    
    class Borg:
        __shared_state = {}
    
        def __init__(self):
            self.__dict__ = self.__shared_state
    

Now lets assume that I want to create context based objects of the Borg class,
based on parameters I pass while creating an object. Is this a correct way to
create Borg pattern ( state sharing) for multiple contexts ?

    
    
    import random
    import cPickle
    
    class Borg:
        __shared_state = { }
    
        def __init__(self,*args,**kwargs):
            context_key = hash('{0}{1}'.format(cPickle.dumps(args),cPickle.dumps(kwargs)))
            self.__shared_state.setdefault(context_key, {})
            self.__dict__ = self.__shared_state[context_key]
            print(self.__shared_state)
    
        def set_random_property(self):
            self.num =  str(random.randint(1,100000))
    
    
    
    a = Borg(x='ONE')
    a.set_random_property()
    b = Borg(x = 'TWO')
    b.set_random_property()
    c = Borg(x = 'TWO')
    print('a with ONE has num:{0}'.format(a.num))
    print('b with TWO has num:{0}'.format(b.num))
    print('c with TWO has num:{0}'.format(c.num))
    

**output**

    
    
    {7373348246660160089: {}}
    {7373348246660160089: {'num': '18322'}, 3334843421982509183: {}}
    {7373348246660160089: {'num': '18322'}, 3334843421982509183: {'num': '33084'}}
    a with ONE has num:18322
    b with TWO has num:33084
    c with TWO has num:33084
    

Works correctly. Is there a way to improvise this pattern ? Or any better
alternatives available for python 2.7 ?


Answer: No, what you use is what I'd use; use a dictionary for the shared states.

You can simplify it _slightly_ by using the return value of
`dict.setdefault()` rather than ignore it:

    
    
    def __init__(self, *args, **kwargs):
        context_key = hash('{0}{1}'.format(cPickle.dumps(args),cPickle.dumps(kwargs)))
        self.__dict__ = self.__shared_state.setdefault(context_key, {})
    

All this can be encapsulated in a metatype:

    
    
    class PerArgsBorgMeta(type):
        def __new__(mcls, name, bases, attrs):
            cls = super(PerArgsBorgMeta, mcls).__new__(mcls, name, bases, attrs)
            setattr(cls, '_{}__shared_state'.format(name), {})
            return cls
    
        def __call__(cls, *args, **kwargs):
            instance = super(PerArgsBorgMeta, cls).__call__(*args, **kwargs)
            context_key = hash('{0}{1}'.format(cPickle.dumps(args),cPickle.dumps(kwargs)))
            state = getattr(cls, '_{}__shared_state'.format(cls.__name__))
            instance.__dict__ = state.setdefault(context_key, {})
            return instance
    

Then use this as a `__metaclass__` attribute on the class:

    
    
    class SomeBorgClass:
        __metaclass__ = PerArgsBorgMeta
    

Do note that using `hash(cPickle.dumps(kwargs))` will still create distinct
hashes for dictionaries with collisions:

    
    
    >>> import cPickle
    >>> hash(cPickle.dumps({'a': 42, 'i': 81}))
    -7392919546006502834
    >>> hash(cPickle.dumps({'i': 81, 'a': 42}))
    2932616521978949826
    

The same applies to _sets_. Sorting (recursively if you must be exhaustive)
can help here, but be careful that you don't then produce false-positives
between, say, a set passed in as a value, and a tuple with the same values in
it used instead. There are increasingly convoluted work-arounds possible for
each of these, but at some point you just have to accept the limitation rather
than complicate the hashing code more still.

