<jupyter_start><jupyter_text>Example 1:Let’s say we want to implement a counter to record how many time the word has been repeated. The first thing you may want to do is to define a dictionary in global scope, and then create a function to add in the words as key into this dictionary and also update the number of times it repeated. Below is the sample code:<jupyter_code>counter = dict()

def count_word(word):  
    global counter  
    
    # some validation ...
    word = word.lower()
    if word not in ['the', 'or', 'and', 'is', 'am', 'are'] and not word.isdigit():
        counter[word] = counter.get(word, 0) + 1
        return counter[word]
    
    return 0

text = 'Java Python Java Java Python C# 1234 the'
for word in text.split():
    count_word(word)
counter
count_word('python')
counter[1234] = 1
counter
counter

# To make sure the count_word function updates the correct “counter”,
# we need to put the global keyword to explicitly tell Python interpreter
# to use the “counter” defined in global scope, not any variable we accidentally
# defined with the same name in the local scope (within this function).<jupyter_output><empty_output><jupyter_text>issue1 :The global variable is accessible to any of the other functions and you cannot guarantee your data won’t be modified by others.  issue2 : The global variable exists in the memory as long as the program is still running, so you may not want to create so many global variables if not necessary.<jupyter_code>#### Solution: bound the variable with the function inside another function

def word_counter():
    counter = dict()
    
    def count(word):
        nonlocal counter
        word = word.lower()
        if word not in ['the', 'or', 'and', 'is', 'am', 'are'] and not word.isdigit():
            counter[word] = counter.get(word, 0) + 1
            return counter[word]
        else:
            return 0
        
    return count<jupyter_output><empty_output><jupyter_text>the counter dictionary is hidden from the public access and the functionality remains the same.<jupyter_code>counter_ = word_counter()
text = 'Java Python Java Java Python C# 1234 the'
for word in text.split():
    counter_(word)
counter_('python')<jupyter_output><empty_output><jupyter_text>Dont worry! It is a closure-------  Closures Functions defined inside another function can access the outer (nonlocal) variables<jupyter_code>def outer():
    x = 'python' # non-local variable x (or free variable)
    def inner(): # a function defined inside the outer function 
        print(f'{x} rocks') # and referenced x, which is defined in the outer function
    return inner<jupyter_output><empty_output><jupyter_text>when we consider inner, we really are looking at two things:+ the function inner+ the free variable x (with current value python)  x is not in the local scope of inner, it is in somewhere else  This two things have to __bound together__ . It is called a closure.closure is a nested function remembers and has access to variables in the scope of the function in which it is defined.A Closure is a __function__ plus an extended scope that contains the __free variables__<jupyter_code>fn = outer() # fn is (inner + free variable). it creates inner (no run)
fn() # 
type(fn)
fn.__code__.co_freevars<jupyter_output><empty_output><jupyter_text>As we can see, `x` is a free variable in the closure.  Why do we use closures?1. Hide data with Python closure    Example 12. Convert small class to function with Python closure    Example 23. Decorators    Next part      Example 2Occasionally in your project, you may want to implement a small utility class to do some simple task. Let’s take a look at the below example:<jupyter_code>url = 'https://divar.ir/s/{city}/{item}?price={min_price}-{max_price}'.format(
    city = 'tehran', item = 'car', min_price = 100000000, max_price = 200000000)
print(url)
import requests

class RequestMaker:
    def __init__(self, base_url):
        self.url = base_url
        
    def request(self, **kwargs):
        return requests.get(self.url.format_map(kwargs))
divar = RequestMaker('https://divar.ir/s/{city}/{item}?price={min_price}-{max_price}')
divar.request(city = 'tehran', item = 'car', min_price = 100000000, max_price = 200000000)
divar.request(city = 'new york', item = 'car', min_price = 100000000, max_price = 200000000)<jupyter_output><empty_output><jupyter_text>Since you’ve already seen in the word counter example, the closure can also hold the data for your later use, the above class can be converted into a function with closure:<jupyter_code>import requests

def request_maker(url):
    def make_request(**kwargs):
        return requests.get(url.format_map(kwargs))
    return make_request<jupyter_output><empty_output><jupyter_text>The code becomes more concise and achieves the same result. Take note that in the above code, we are able to pass in the arguments into the nested function with \*\*kwargs (or \*args).<jupyter_code>divar = request_maker('https://divar.ir/s/{city}/{item}?price={min_price}-{max_price}')
divar(city = 'tehran', item = 'car', min_price = 100000000, max_price = 200000000)<jupyter_output><empty_output><jupyter_text>Multiple Instances of Closures<jupyter_code>def pow(n):
    # n is local to pow
    def inner(x):
        # x is local to inner
        return x ** n
    return inner<jupyter_output><empty_output><jupyter_text>In this example, `n`, in the function `inner` is a free variable, so we have a closure that contains `inner` and the free variable `n`<jupyter_code>square = pow(2)
square(5)
cube = pow(3)
cube(5)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09197860962566845, 'fraction_numerical': 0.018538324420677363, 'mean_word_length': 4.239028944911298, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 8, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '461992', 'license': 'MIT', 'n_tokens_mistral': 1729, 'n_tokens_neox': 1618, 'n_words': 668, 'path': 'Functional Programming/Scopes, Closures and Decorators/03 - Closures.ipynb', 'repo_name': 'Rafiei-tayebe/AP4002', 'size': '5610'}