<jupyter_start><jupyter_text>Closure Applications (Part 1) In this example we are going to build an averager function that can average multiple values.The twist is that we want to simply be able to feed numbers to that function and get a running average over time, not average a list which requires performing the same calculations (sum and count) over and over again.<jupyter_code>class Averager:
    def __init__(self):
        self.numbers = []
    
    def add(self, number):
        self.numbers.append(number)
        total = sum(self.numbers)
        count = len(self.numbers)
        return total / count
a = Averager()
a.add(10)
a.add(20)
a.add(30)<jupyter_output><empty_output><jupyter_text>We can do this using a closure as follows:<jupyter_code>def averager():
    numbers = []
    def add(number):
        numbers.append(number)
        total = sum(numbers)
        count = len(numbers)
        return total / count
    return add
a = averager()
a(10)
a(20)
a(30)<jupyter_output><empty_output><jupyter_text>Now, instead of storing a list and reclaculating `total` and `count` every time wer need the new average, we are going to store the running total and count and update each value each time a new value is added to the running average, and then return `total / count`.Let's start with a class approach first, where we will use instance variables to store the running total and count and provide an instance method to add a new number and return the current average.<jupyter_code>class Averager:
    def __init__(self):
        self._count = 0
        self._total = 0
    
    def add(self, value):
        self._total += value
        self._count += 1
        return self._total / self._count
a = Averager()
a.add(10)
a.add(20)
a.add(30)<jupyter_output><empty_output><jupyter_text>Now, let's see how we might use a closure to achieve the same thing.<jupyter_code>def averager():
    total = 0
    count = 0
    
    def add(value):
        nonlocal total, count
        total += value
        count += 1
        return 0 if count == 0 else total / count
    
    return add
        
a = averager()
a(10)
a(20)
a(30)<jupyter_output><empty_output><jupyter_text>Generalizing this example We saw that we were essentially able to convert a class to an equivalent functionality using closures. This is actually true in a much more general sense - very often, classes that define a single method (other than initializers) can be implemented using a closure instead.Let's look at another example of this. Suppose we want something that can keep track of the running elapsed time in seconds.<jupyter_code>from time import perf_counter
class Timer:
    def __init__(self):
        self._start = perf_counter()
    
    def __call__(self):
        return (perf_counter() - self._start)
a = Timer()<jupyter_output><empty_output><jupyter_text>Now wait a bit before running the next line of code:<jupyter_code>a()<jupyter_output><empty_output><jupyter_text>Let's start another "timer":<jupyter_code>b = Timer()
print(a())
print(b())<jupyter_output>3.1155557760000008
0.29124831300000054<jupyter_text>Now let's rewrite this using a closure instead:<jupyter_code>def timer():
    start = perf_counter()
    
    def elapsed():
        # we don't even need to make start nonlocal 
        # since we are only reading it
        return perf_counter() - start
    
    return elapsed
x = timer()
x()
y = timer()
print(x())
print(y())
print(a())
print(b())
print(x())
print(y())<jupyter_output>4.713114046000001
1.888806163
0.9454463620000002
0.5958142350000006
{'fraction_non_alphanumeric': 0.08768971332209106, 'fraction_numerical': 0.035975267003934795, 'mean_word_length': 3.622077922077922, 'pattern_counts': {'":': 1, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '321698', 'license': 'Apache-2.0', 'n_tokens_mistral': 1176, 'n_tokens_neox': 1067, 'n_words': 397, 'path': 'python-tuts/0-beginner/6-Scopes, Closures, Decorators/04 - Closure Applications - Part 1.ipynb', 'repo_name': 'AadityaGupta/Artificial-Intelligence-Deep-Learning-Machine-Learning-Tutorials', 'size': '3558'}