2

According to this question I thought that I had understood the closure concept in a singleton pattern.

This is what works:

def singleton(cls):
    instances = {}
    print instances # control the state of instances
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class cls(object):
    pass

@singleton
class abc(object):
    pass

Then I thought that a dictionary would be too much as there is only one instance in the instances dictionary per class. So if if say a = abc() and c = cls() and then abc() and cls() again, there will be two different dictionaries printed, which is perfectly alright and the singleton works.

So I wanted to use a single variable instead of a dictionary:

def singleton(cls):
    instance = None
    def get_instance(*args, **kwargs):
        if instance is None:
            instance = cls(*args, **kwargs)
        return instance
    return get_instance

But this lead to:

<ipython-input-22-2961f5b89daf> in get_instance(*args, **kwargs)
      2     instance = None
      3     def get_instance(*args, **kwargs):
----> 4         if instance is None:
      5             instance = cls(*args, **kwargs)
      6         return instance

UnboundLocalError: local variable 'instance' referenced before assignment

Why does it break? I only wanted to use a single variable instead of a whole dictionary, one works and one causes an assignment error.

Community
  • 1
  • 1
Xiphias
  • 4,468
  • 4
  • 28
  • 51

3 Answers3

2

The assignment to instance in the modified version causes Python to create a new variable named instance local to get_instance. This new variable shadows the outer instance; get_instance cannot read or modify the outer variable. The dict is used to get around this. (In Python 3, you could instead declare nonlocal instance in get_instance to avoid the problem.)

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Thank you. So this is a special behaviour of a dict? What do you mean by "The dict is used to get around this"? – Xiphias Nov 29 '13 at 09:25
  • 1
    @Tobias: Item assignment, such as in `instances[cls] = cls(*args, **kwargs)`, does not create a new local variable. We could have used a list and assigned `l[0]` instead of using the dict; it likely would have been a bit faster, actually. – user2357112 Nov 29 '13 at 09:27
  • Ah, I see! `instance = ...` creates a local variable in the scope of `get_instance` where `instances[key] = ...` does use the available dict outside of the scope? Is there a name for this behaviour? Tricky. – Xiphias Nov 29 '13 at 09:30
  • 1
    No name. It's a straightforward but surprising consequence of the way Python determines what a function's local variables are. – user2357112 Nov 29 '13 at 09:32
  • This is very interesting. Thank you for contributing to the solution. For everyone else who thinks about this problem: http://stackoverflow.com/questions/4851463/python-closure-write-to-variable-in-parent-scope is another question where it's not about singletons but the same concept of accessing an outer scope. – Xiphias Nov 29 '13 at 09:36
1

The reason you want a dict is that it uses the type of the class as the key and value is the single instance that has been created.

class Thing: 
    pass

instances[Thing] = Thing()

And if you print 'instances', you'll see:

{__main__.Thing: <__main__.Thing instance at 0x104349cf8>}
  • The value is the class, or rather a reference to the class. For means of comparison.

  • The instance is an actual living breathing instantiation of that class.

You could store your classes in an array, but you'd have to do essentially the same type of work that a dict does - looking up a value by it's key.

--edit--

The reason the non-dict version errors with being unable to find 'instance' is that the inner scope doesn't have 'instance' defined. You can pass it in on the get_instance line but.. thats not going to work the way you want I don't think.

Without the dict to look up the instance of a class, how will you know who the instance is?

def singleton(cls):
    instance = None 
    def get_instance(instance=instance, *args, **kwargs):        
        if instance is None:
            instance = cls(*args, **kwargs)
        return instance
    return get_instance
synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
  • Thanks. However, this does not quite explain the different behaviour of the dict version and the non-dict version. – Xiphias Nov 29 '13 at 09:29
  • Modified my answer to explain the actual error you're finding - but it's really a disservice considering that it continues you down the wrong path. But, if you insist.. – synthesizerpatel Nov 29 '13 at 09:37
  • Thanks :-) It's always nice to understand the concept. You are right, I think, because of `instance=instance` being handed over as a function argument, there should be no bounds to the original instance variable. And without it, it also would not work. However, thanks for explaining! – Xiphias Nov 29 '13 at 09:41
0

If the variable is on the same class and is global then you can invite it like: this.instance, so if this.instance == 5: some code.

Lindi
  • 1
  • Hi, could you please elaborate on that a bit more? I know `self`, but where does your `this` come from? It's Python, there is no `this` by default. Or did I get something basic wrong? – Xiphias Nov 29 '13 at 09:22