10.16. Functional Pattern 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
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.
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.
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
10.16.1. Recap¶
Functions can define their own variables:
>>> def main():
... firstname = 'Mark'
... lastname = 'Watney'
... print(f'Hello {firstname} {lastname}')
>>>
>>>
>>> main()
Hello Mark Watney
Function can access data from outer scope:
>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>> def main():
... print(f'Hello {firstname} {lastname}')
>>>
>>>
>>> main()
Hello Mark Watney
10.16.2. Nested Function¶
Function inside the function
Nested functions can access the variables of the enclosing scope
>>> def main():
... firstname = 'Mark'
... lastname = 'Watney'
... def say_hello():
... print(f'Hello {firstname} {lastname}')
... say_hello()
>>>
>>>
>>> main()
Hello Mark Watney
>>>
>>> say_hello()
Traceback (most recent call last):
NameError: name 'say_hello' is not defined
10.16.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.
>>> def main():
... firstname = 'Mark'
... lastname = 'Watney'
... def say_hello():
... print(f'Hello {firstname} {lastname}')
... return say_hello
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]
>>> result = main()
>>> result()
Hello Mark Watney
Remove outer function:
>>> result = main()
>>> del main
>>> result()
Hello Mark Watney
10.16.4. Why?¶
Closures provides some form of data hiding
Closures can avoid use of global variables
Useful for replacing hard-coded constants
>>> def main():
... firstname = 'Mark'
... lastname = 'Watney'
...
... def say_hello():
... print(f'Hello {firstname} {lastname}')
...
... return say_hello
>>> hello = main()
>>> hello()
Hello Mark Watney
>>> hello = main()
>>> del main
>>> hello()
Hello Mark Watney
>>> hello
<function main.<locals>.say_hello at 0x...>
10.16.5. How Objects Were Born¶
main
- constructorsay_hello
- instance methodfirstname
- instance variable (field)lastname
- instance variable (field)
>>> def main():
... firstname = 'Mark'
... lastname = 'Watney'
...
... def say_hello():
... print(f'Hello {firstname} {lastname}')
...
... return locals()
...
>>> x = main()
>>>
>>> x['firstname']
'Mark'
>>>
>>> x['lastname']
'Watney'
>>>
>>> x['say_hello']()
Hello Mark Watney
>>> x
{'say_hello': <function main.<locals>.say_hello at 0x...>,
'firstname': 'Mark',
'lastname': 'Watney'}
10.16.6. References¶
10.16.7. Assignments¶
"""
* Assignment: Functional Closure Define
* Complexity: easy
* Lines of code: 4 lines
* Time: 3 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():
...
"""
* Assignment: Functional 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 = ...