10.13. Functional Scope
Values defined in function does not leak out
Functions has access to global values
Shadowing is when you define variable with name identical to the one from outer scope
Shadowing in a function is valid only in a function
After function return, the original value of a shadowed variable is restored
global
keyword allows modification of global variableUsing
global
keyword is considered as a bad practice
In Python, a function scope refers to the area of a program where a variable is accessible. A variable's scope is determined by where it is defined in the program.
When a variable is defined inside a function, it has local scope, which means that it can only be accessed within that function. Local variables are destroyed when the function returns.
Here's an example of using local variables in Python:
>>> def run():
... x = 1
... print(x)
>>>
>>> run()
1
>>> print(x)
Traceback (most recent call last):
NameError: name 'x' is not defined
In this example, the x
variable is defined inside the run()
function. It has local scope and can only be accessed within the function.
When the function is called, it prints the value of x
. When the function
returns, the x
variable is destroyed.
If we try to access the x variable outside the function, we get a
NameError
because the variable is not defined in the global scope.
Variables defined outside of any function have global scope, which means that they can be accessed from anywhere in the program. Global variables are not destroyed when a function returns.
Here's an example of using global variables in Python:
>>> x = 1
>>>
>>> def run():
... print(x)
>>>
>>> run()
1
>>> x
1
In this example, the x
variable is defined outside of any function and has
global scope. It can be accessed from within the run()
function
and from outside the function. When the function is called, it prints the
value of x
. When the program ends, the x
variable is not destroyed
because it has global scope.
Using function scope helps to keep variables organized and prevent naming conflicts between different parts of a program.
10.13.1. Local Scope
Values defined in function does not leak out
SetUp:
>>> del run
>>> del x
Code:
>>> def run():
... x = 1
... print(x)
Result:
>>> x
Traceback (most recent call last):
NameError: name 'x' is not defined
>>>
>>> run()
1
>>>
>>> x
Traceback (most recent call last):
NameError: name 'x' is not defined
10.13.2. Locals
SetUp:
>>> del run
Code:
>>> def run():
... x = 1
... print(locals())
Result:
>>> run()
{'x': 1}
10.13.3. Global Scope
Functions has access to global values
SetUp:
>>> del run
Code:
>>> x = 1
>>>
>>> def run():
... print(x)
Result:
>>> x
1
>>>
>>> run()
1
>>>
>>> x
1
10.13.4. Globals
In the globals scope:
globals()
has all the objects from the global scopeIn the global scope:
globals()
is the same aslocals()
In the function scope:
globals()
is the same as in global scopeIn the function scope:
locals()
has only variables from inside of a function
SetUp:
>>> del run
>>> del x
Code:
>>> x = 1
>>>
>>> def run():
... print(globals())
Result:
>>> run()
{'__name__': '__main__',
'__builtins__': {...},
'x': 1,
'run': <function run at 0x...>}
10.13.5. Shadowing
When variable in function has the same name as in outer scope
Shadowing in a function is valid only in a function
Shadowed variable will be deleted upon function return
After function return, the original value of a shadowed variable is restored
SetUp:
>>> del run
>>> del x
Code:
>>> x = 1
>>>
>>> def run():
... x = 10
... print(x)
Result:
>>> x
1
>>>
>>> run()
10
>>>
>>> x
1
10.13.6. Modify Global
This does not work with non-nested functions:
SetUp:
>>> del run
>>> del x
Code:
>>> x = 1
>>>
>>> def run():
... x += 10
... print(x)
Result:
>>> run()
Traceback (most recent call last):
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
10.13.7. Global Keyword
global
keyword allows modification of global variableUsing
global
keyword is considered as a bad practice
SetUp:
>>> del run
>>> del x
Code:
>>> x = 1
>>>
>>> def run():
... global x
... x = 10
... print(x)
Result:
>>> x
1
>>>
>>> run()
10
>>>
>>> x
10
10.13.8. NonLocal Scope
The nonlocal keyword won't work on local or global variables and therefore must be used to reference variables in another scope except the global and local one. The nonlocal keyword is used in nested functions to reference a variable in the parent function. [1]
SetUp:
>>> del run
>>> del x
Code:
>>> x = 1
>>>
>>> def run():
... x = 2
... def inner():
... x += 10
... print(x)
... inner()
Result:
>>> run()
Traceback (most recent call last):
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
10.13.9. Nonlocal
Python nonlocal keyword is used to reference a variable in the nearest scope
SetUp:
>>> del run
>>> del x
Code:
>>> x = 1
>>>
>>> def run():
... x = 10
... def inner():
... nonlocal x
... x += 100
... print(x)
... inner()
Result:
>>> run()
110
Problem:
>>> x = 1
>>>
>>> def run():
... nonlocal x
... x += 1
... print(x)
Traceback (most recent call last):
SyntaxError: no binding for nonlocal 'x' found
10.13.10. Use Case - 1
Simulate user input (for test automation)
>>> from unittest.mock import MagicMock
>>> input = MagicMock(side_effect=['lastname'])
>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>>
>>> varname = input('Type variable name: ') #input: 'lastname'
>>>
>>> globals()[varname]
'Watney'
10.13.11. Use Case - 2
>>> class Iris:
... def __init__(self, sl, sw, pl, pw):
... ...
>>>
>>> class Setosa(Iris):
... pass
>>>
>>> class Virginica(Iris):
... pass
>>>
>>> class Versicolor(Iris):
... pass
>>>
>>>
>>> globals()
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'Setosa': <class '__main__.Setosa'>,
'Virginica': <class '__main__.Virginica'>,
'Versicolor': <class '__main__.Versicolor'>}
>>> *measurement, species = (5.1, 3.5, 1.4, 0.2, 'Setosa')
>>> cls = globals()[species]
>>> cls(*measurement)
<__main__.Setosa object at 0x...>
10.13.12. References
10.13.13. Assignments
# %% 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
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Functional Scope Global
# - Difficulty: easy
# - Lines: 5
# - Minutes: 3
# %% English
# 1. Define in global scope `SELECT: set[str]` with values:
# `'setosa', 'versicolor'`
# 2. Define function `sumif(values, species)`
# 3. Function sums `values`, only when `species` is in `SELECT`
# 4. When `species` is not in `select` return `0` (zero)
# 5. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj w przestrzeni globalnej `SELECT: set[str]` z wartościami:
# `'setosa', 'versicolor'`
# 2. Zdefiniuj funkcję `sumif(values, species)`
# 3. Funkcja sumuje `values`, tylko gdy `species` jest w `SELECT`
# 4. Gdy `species` nie występuje w `select` zwróć `0` (zero)
# 5. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction, signature
>>> assert type(SELECT) is set, \
'Define in global scope `SELECT: set[str]`'
>>> assert 'setosa' in SELECT, \
'Value setosa must be in SELECT'
>>> assert 'versicolor' in SELECT, \
'Value versicolor must be in SELECT'
>>> assert isfunction(sumif), \
'Define function `sumif(values, species)`'
>>> signature(sumif)
<Signature (values, species)>
>>> sum(sumif(X,y) for *X, y in DATA[1:])
49.1
"""
DATA = [
('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
(5.8, 2.7, 5.1, 1.9, 'virginica'),
(5.1, 3.5, 1.4, 0.2, 'setosa'),
(5.7, 2.8, 4.1, 1.3, 'versicolor'),
(6.3, 2.9, 5.6, 1.8, 'virginica'),
(6.4, 3.2, 4.5, 1.5, 'versicolor'),
(4.7, 3.2, 1.3, 0.2, 'setosa'),
]
# Sums values, when species is in SELECT, else return 0 (zero)
# type: Callable[[list[float], str], float]
def sumif():
...