11.15. Functional Pattern Closure

11.15.1. About

  • 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

In Python, a closure is a function object that has access to variables in its enclosing lexical scope, even when the function is called outside that scope. A closure is created when a nested function references a value from its enclosing scope.

Closures are useful for creating functions that have state or that need to remember values from previous calls. They can also be used to create decorators, which are functions that modify the behavior of other functions.

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

11.15.2. Example

Here's an example of using a closure in Python:

>>> def outer_function(x):
...     def inner_function(y):
...         return x + y
...     return inner_function
>>>
>>> add_five = outer_function(5)
>>> result = add_five(10)
>>>
>>> print(result)
15

In this example, the outer_function() function takes a value x as its argument and defines a nested function inner_function(). The inner_function() function references the x variable from its enclosing scope.

When the outer_function() function is called with the argument 5, it returns the inner_function() function. This function is assigned to the variable add_five.

The add_five() function is then called with the argument 10, which is added to the x value of 5 from the enclosing scope. The result of 15 is returned and stored in the result variable.

The add_five() function is a closure because it has access to the x variable from its enclosing scope, even though it is called outside that scope. The closure allows the add_five() function to "remember" the value of x from when it was defined.

11.15.3. Recap

  • Function can access data from outer scope

  • Functions inside their scope can define variables (local scope)

  • Functions inside their scope can define functions (inner functions)

  • Inner functions have access to variables defined in their outer scope (local scope of outer function)

Function can access data from outer scope:

>>> name = 'Mark'
>>>
>>> def run():
...     return f'Hello {name}'
>>>
>>>
>>> run()
'Hello Mark'

Functions inside their scope can define variables (local scope):

>>> def run():
...     name = 'Mark'
...     return f'Hello {name}'
>>>
>>>
>>> run()
'Hello Mark'

Functions inside their scope can define functions (inner functions):

>>> def run():
...     def say_hello():
...         return f'Hello'

Inner functions have access to variables defined in their outer scope (local scope of outer function):

>>> def run():
...     name = 'Mark'
...     def say_hello():
...         return f'Hello {name}'

11.15.4. Return Locals

>>> def run():
...     name = 'Mark'
...     def say_hello():
...         return f'Hello {name}'
...     return locals()
>>>
>>>
>>> result = run()
>>>
>>> result  
{'say_hello': <function main.<locals>.say_hello at 0x107c5fc40>, 'name': 'Mark'}

11.15.5. What is closure?

  • Closures provides some form of data hiding

  • Closures can avoid use of global variables

  • Useful for replacing hard-coded constants

>>> def run():
...     name = 'Mark'
...     def say_hello():
...         return f'Hello {name}'
...     return locals()
>>>
>>>
>>> result = run()

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.

>>> result['say_hello']  
<function main.<locals>.say_hello at 0x107c5fc40>
>>> result['say_hello']()
'Hello Mark'

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]

>>> del result['name']
>>>
>>> result  
{'say_hello': <function main.<locals>.say_hello at 0x107c5e340>}
>>>
>>> result['say_hello']  
<function main.<locals>.say_hello at 0x107c5e340>
>>>
>>> result['say_hello']()
'Hello Mark'

11.15.6. How Objects Were Born

  • user - constructor

  • fn - constructor argument (firstname)

  • ln - constructor argument (lastname)

  • firstname - instance variable (field)

  • lastname - instance variable (field)

  • login - instance function (method)

  • logout - instance function (method)

>>> def user(fn, ln):
...     firstname = fn
...     lastname = ln
...
...     def login():
...         return f'User {firstname} logged-in'
...
...     def logout():
...         return f'User {firstname} logged-out'
...
...     return locals()
>>>
>>>
>>> mark = user('Mark', 'Watney')
>>>
>>> mark['firstname']
'Mark'
>>>
>>> mark['lastname']
'Watney'
>>>
>>> mark['login']()
'User Mark logged-in'
>>>
>>> mark['logout']()
'User Mark logged-out'
>>>
>>> mark  
{'firstname': 'Mark',
 'lastname': 'Watney',
 'login': <function user.<locals>.login at 0x107c5fc40>,
 'logout': <function user.<locals>.logout at 0x107c5e2a0>}

11.15.7. Decorators

Function based decorators:

>>> def mydecorator(func):
...     print('on init')
...     def wrapper(*args, **kwargs):
...         print('on call')
...         return func(*args, **kwargs)
...     return wrapper

Class based decorators:

>>> class MyDecorator:
...     def __init__(self, func):
...         print('on init')
...         self.func = func
...
...     def __call__(self, *args, **kwargs):
...         print('on call')
...         return self.func(*args, **kwargs)

11.15.8. References

11.15.9. Assignments

# %% About
# - Name: Functional Closure Call
# - Difficulty: easy
# - Lines: 9
# - Minutes: 5

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% 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ść

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> 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
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% Imports

# %% Types
from typing import Callable
check: Callable[[Callable], Callable]
hello: Callable[[], str]
result: Callable[[], str]

# %% Data
def check():
    ...

def hello():
    ...

# %% Result
result = ...