Search code examples
pythonoopclosuresglobaldefault-parameters

Alternating the use of classes/globals with closures in Python


I came across closures in python, and I've been tinkering around the subject.

Please Correct me if I'm wrong here, but what I understood for when to use closures (generally) is that it can be used as a replacement of small classes (q1) and to avoid the use of globals (q2).

Q1: [replacing classes]

Any instance created from the datafactory class will have it's own list of data, and hence every appending to that object's list will result in an incremental behavior. I understand the output from an OO POV.

class datafactory():

    def __init__(self):
        self.data = []

    def __call__(self, val):

        self.data.append(val)
        _sum = sum(self.data)

        return _sum

incrementwith = datafactory()
print(incrementwith(1))
print(incrementwith(1))
print(incrementwith(2))
OUTPUT: 
1
2
4

I tried replacing this with a closure, it did the trick, but my understanding to why/how this is happening is a bit vague.

def data_factory():
    data = []

    def increment(val):

        data.append(val)
        _sum = sum(data)
        return _sum

    return increment

increment_with = data_factory()

print(increment_with(1))
print(increment_with(1))
print(increment_with(2))
OUTPUT:
1
2
4

What I'm getting is that the data_factory returns the function definition of the nested increment function with the data variable sent along as well, I would've understood the output if it was something like this:

1
1
2

But how exactly the data list persists with every call? Shouldn't variables defined in a function die after the function finishes execution and get regenerated and cleared out with the next fn call?

Note: I know that this behavior exists normally in a function defined with default parameters like def func(val, l = []): where the list will not be cleared on every fn call, but rather be updated with a new element/append, which is also something that I do not fully understand.

I would really appreciate an academic explanation to what happens in both scenarios (OO and closures).

Q2: [replacing use of global]

Is there a way using closures to increment the following variable without using globals or a return statement ?

a = 0
print("Before:", a) # Before: 0

def inc(a):
    a += 1

print("After:", a) # After: 0

Thank you for your time.


Solution

  • For the first question, I found after some digging that passing mutables as default parameters isn't really a good move to make: https://florimond.dev/blog/articles/2018/08/python-mutable-defaults-are-the-source-of-all-evil/#:~:text=of%20this%20mess.-,The%20problem,or%20even%20a%20class%20instance.