4.6. Closures

4.6.1. Rationale

  • Function local variables are stored on the stack (function stack frame)

  • Inner functions have access to outer functions variables (access to outer function stack)

  • In order to that work, you can call inner function only when outer function is running

  • https://youtu.be/t86v3N4OshQ?t=954

def f(x):
    def g(y):
        return x + y
    return g

4.6.2. Nested Function

  • Function inside the function

  • Nested functions can access the variables of the enclosing scope

def run():
    def hello():
        print('hello')
    return hello


hello()
# Traceback (most recent call last):
# NameError: name 'hello' is not defined

run()
# <function run.<locals>.hello at 0x104e13700>

run()()
# hello

result = run()
result()
# hello

4.6.3. What is closure?

  • technique by which the data is attached to some code even after end of those other original functions is called as closures

  • Closures can avoid use of global variables and provides some form of data hiding

  • When the interpreter detects the dependency of inner nested function on the outer function, it stores or makes sure that the variables in which inner function depends on are available even if the outer function goes away

  • Method of binding data to a function without actually passing them as parameters is called closure

  • It is a function object obj that remembers values in enclosing scopes even if they are not present in memory

  • Closures provide some sort of data hiding as they are used as callback functions

  • This helps us to reduce the use of global variables

  • Useful for replacing hard-coded constants

  • Closures prove to be efficient way when we have few functions in our code

firstname = 'Mark'
lastname = 'Watney'

def hello():
    print(firstname, lastname)


hello()
# Mark Watney
firstname = 'Mark'

def run():
    lastname = 'Watney'

    def hello():
        print(firstname, lastname)

    return hello


result = run()
result()
# Mark Watney
firstname = 'Mark'

def run():
    lastname = 'Watney'

    def hello():
        print(firstname, lastname)

    return hello


result = run()
del run
result()
# Mark Watney

4.6.4. Assignments

Code 4.8. Solution
"""
* Assignment: Function Closure Define
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
    1. Define function `check` which takes `func: Callable` as an argument
    2. Define closure function `wrapper` inside `check`
    3. Function `wrapper` takes `*args` and `**kwargs` as arguments
    4. Function `wrapper` returns `None`
    5. Function `check` must return `wrapper: Callable`
    X. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `check`, która przyjmuje `func: Callable` jako argument
    2. Zdefiniuj funkcję closure `wrapper` wewnątrz `check`
    3. Funkcja `wrapper` przyjmuje `*args` i `**kwargs` jako argumenty
    4. Funkcja `wrapper` zwraca `None`
    5. Funkcja `check` ma zwracać `wrapper: Callable`
    X. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> assert callable(check)
    >>> assert callable(check(lambda x: x))
    >>> result = check(lambda x: x).__call__()
    >>> result is None
    True
"""


Code 4.9. Solution
"""
* Assignment: Function Closure Call
* Complexity: easy
* Lines of code: 9 lines
* Time: 5 min

English:
    1. Define function `check` with parameter `func: Callable`
    2. Define closure function `wrapper` inside `check`
    3. Function `wrapper` takes arbitrary number of positional and keyword arguments
    4. Function `wrapper` prints `hello from wrapper` on the screen
    5. Function `check` must return `wrapper: Callable`
    6. Define function `hello()` which prints `hello from function`
    7. Define `result` with result of calling `check(hello)`
    8. Delete `check` using `del` keyword
    9. Call `result`
    10. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `check` z parametrem `func: Callable`
    2. Zdefiniuj funkcję closure `wrapper` wewnątrz `check`
    3. Funkcja `wrapper` przyjmuje dowolną ilość argumentów pozycyjnych i nazwanych
    4. Funkcja `wrapper` wypisuje `hello from wrapper`
    5. Funkcja `check` ma zwracać `wrapper: Callable`
    6. Zdefiniuj funkcję `hello()`, która wypisuje `hello from function`
    7. Zdefiniuj zmienną `result`, która jest wynikiem wywołania `check(hello)`
    8. Skasuj `check` za pomocą słowa kluczowego `del`
    9. Wywołaj `result`
    10. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> from inspect import isfunction
    >>> assert isfunction(hello)
    >>> assert isfunction(result)
    >>> assert not hasattr(__name__, 'check')

    >>> hello()
    hello from function

    >>> result()
    hello from wrapper

    >>> check()
    Traceback (most recent call last):
    NameError: name 'check' is not defined
"""