How is the below decorator make Database a singleton?

The way I am understanding it is that the @singleton decorator makes the Database class to a function that returns a Database object. However, since it's now acting like a function, when the function call ends, function variables should be dead (e.g. instances variable).

The weird part is that when I call this function the second time, it seems like the instance variable is still alive and store the previous information, even id is the same. How is this happening? Can someone please help me understand this?

Thank you in advance

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        nonlocal instances
        print(instances, id(instances))
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

@singleton
class Database:
    def __init__(self):
        print('Loading database')

if __name__ == '__main__':
    db1 = Database() # first call.
    db2 = Database() # second call. why is this returning the same id?

Output

{} 4011784
Loading database
{<class '__main__.Database'>: <__main__.Database object at 0x0577C148>} 4011784

Solution 1:

There are several concepts which together make this possible. Let's take a look at your current code:

@singleton
class Database:
    def __init__(self):
        print('Loading database')

The above code can be translated to the following code. The @ syntax is the short form of:

class Database:
    def __init__(self):
        print('Loading database')

print(Database)
Database = singleton(Database) # manually decorated
print(Database)

The class declaration itself is stored to the variable named Database. Afterwards, the variable is replaced with the result of the singleton function call (which is the get_instance function). This means, whenever you create a new instance of Database actually get_instance is now called.

<class '__main__.Database'>
<function singleton.<locals>.get_instance at 0x7f8e82738310>

Now let's take a look at the singleton function:

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        nonlocal instances # nonlocal is not required here!
        print(instances, id(instances))
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

The singleton function declares a local instances variable which belongs to the scope of the singleton function. On each call to singleton a newly independent get_instance function (a so-called closure) is created. According to the LEGB rule, the variables of the enclosing scope are "inherit" (this is technically not correct, but simplifies the explanation) to the closure. This means, that instances will stay the same object for every call to a specific get_instance function.

Now let's summarize. As on each call of Database() actually get_instance is called, and as instances stays the same for each call to this specific get_instance function, we can achieve the effect, that the class will be created only once and that the same object will be returned for every succeeding call.