4.13. FuncProg Closure

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

  • 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

  • Closures provides some form of data hiding

  • Closures can avoid use of global variables

  • Useful for replacing hard-coded constants

  • Inner functions implicitly carry references to all of the local variables in the surrounding scope that are referenced by the function

  • Since Python 2.2

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

4.13.1. Recap

Functions can define their own variables:

>>> def run():
...     firstname = 'Mark'
...     lastname = 'Watney'
...     print(f'Hello {firstname} {lastname}')
>>>
>>>
>>> run()
Hello Mark Watney

Function can access data from outer scope:

>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>> def run():
...     print(f'Hello {firstname} {lastname}')
>>>
>>>
>>> run()
Hello Mark Watney

4.13.2. Nested Function

  • Function inside the function

  • Nested functions can access the variables of the enclosing scope

>>> def run():
...     firstname = 'Mark'
...     lastname = 'Watney'
...     def hello():
...         print(f'Hello {firstname} {lastname}')
...     hello()
>>>
>>>
>>> run()
Hello Mark Watney
>>>
>>> hello()
Traceback (most recent call last):
NameError: name 'hello' is not defined

4.13.3. What is closure?

Closure is a technique by which the data is attached to some code even after end of those other original functions is called as closures. 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.

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 1

>>> def run():
...     firstname = 'Mark'
...     lastname = 'Watney'
...     def hello():
...         print(f'Hello {firstname} {lastname}')
...     return hello
>>>
>>>
>>> result = run()
>>> result()
Hello Mark Watney

Remove outer function:

>>> def run():
...     firstname = 'Mark'
...     lastname = 'Watney'
...     def hello():
...         print(f'Hello {firstname} {lastname}')
...     return hello
>>>
>>>
>>> result = run()
>>> del run
>>> result()
Hello Mark Watney

4.13.4. Why?

  • Closures provides some form of data hiding

  • Closures can avoid use of global variables

  • Useful for replacing hard-coded constants

4.13.5. References

1

Martin, Robert C. The S.O.L.I.D. Principles of OO & Agile Design. Year: 2015. Retrieved: 2021-09-22. URL: https://youtu.be/t86v3N4OshQ?t=954

4.13.6. Assignments

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

English:
    1. Define function `check` with `func: Callable` as a parameter
    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`
    6. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `check`, z `func: Callable` jako parametr
    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`
    6. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

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


# Takes func
# Defines wrapper with args, kwargs
# Returns wrapper
# type: Callable[[Callable], Callable]
def check():
    ...


Code 4.30. Solution
"""
* Assignment: FuncProg 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 arguments
       - arbitrary number of keyword arguments
    4. Function `wrapper` returns 'hello from wrapper'
    5. Function `check` must return `wrapper: Callable`
    6. Define function `hello()` which returns '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
       - dowolną ilość argumentów nazwanych
    4. Funkcja `wrapper` zwraca 'hello from wrapper'
    5. Funkcja `check` ma zwracać `wrapper: Callable`
    6. Zdefiniuj funkcję `hello()`, która zwraca '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:
    >>> import sys; sys.tracebacklimit = 0
    >>> 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
"""

# Takes func
# Defines wrapper with args, kwargs
# Wrapper returns 'hello from wrapper'
# Returns wrapper
# type: Callable[[Callable], Callable]
def check():
    ...

# Returns `hello from function`
# type: Callable
def hello():
    ...

# Call check(hello); delete check; call result
# type: Callable[[], str]
result = ...