8.6. 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
8.6.1. Values Leaking¶
Values defined in function does not leak out
>>> def run(a, b=1):
... c = 2
>>>
>>>
>>> print(a)
Traceback (most recent call last):
NameError: name 'a' is not defined
>>>
>>> print(b)
Traceback (most recent call last):
NameError: name 'b' is not defined
>>>
>>> print(c)
Traceback (most recent call last):
NameError: name 'c' is not defined
>>>
>>> run(0)
>>>
>>> print(a)
Traceback (most recent call last):
NameError: name 'a' is not defined
>>>
>>> print(b)
Traceback (most recent call last):
NameError: name 'b' is not defined
>>>
>>> print(c)
Traceback (most recent call last):
NameError: name 'c' is not defined
8.6.2. Outer Scope¶
Functions has access to global values
>>> data = [1, 2, 3]
>>>
>>>
>>> def run():
... print(data)
>>>
>>>
>>> print(data)
[1, 2, 3]
>>>
>>> run()
[1, 2, 3]
>>>
>>> print(data)
[1, 2, 3]
8.6.3. 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
>>> data = [1, 2, 3]
>>>
>>>
>>> def run():
... data = [10, 20, 30] # Shadows name 'data' from outer scope
... print(data)
>>>
>>>
>>> print(data)
[1, 2, 3]
>>>
>>> run()
[10, 20, 30]
>>>
>>> print(data)
[1, 2, 3]
8.6.4. Global¶
global
keyword allows modification of global variableUsing
global
keyword is considered as a bad practice
>>> data = [1, 2, 3]
>>>
>>>
>>> def run():
... global data
... data = [10, 20, 30]
... print(data)
>>>
>>>
>>> print(data)
[1, 2, 3]
>>>
>>> run()
[10, 20, 30]
>>>
>>> print(data)
[10, 20, 30]
8.6.5. Nonlocal¶
Python nonlocal keyword is used to reference a variable in the nearest 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]
>>> def setup():
... data = ['a', 'b', 'c']
... index = -1
...
... def get():
... index += 1
... return data[index]
... return get
>>>
>>>
>>> next = setup()
>>> next()
Traceback (most recent call last):
UnboundLocalError: cannot access local variable 'index' where it is not associated with a value
>>> def setup():
... data = ['a', 'b', 'c']
... index = -1
...
... def get():
... nonlocal index
... index += 1
... return data[index]
... return get
>>>
>>> next = setup()
>>> next()
'a'
8.6.6. Global Scope¶
>>> globals()
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>}
>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>> globals()
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'firstname': 'Mark',
'lastname': 'Watney'}
>>> class User:
... pass
>>>
>>> mark = User()
>>>
>>> globals()
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'User': <class '__main__.User'>,
'mark': <__main__.User object at 0x...>}
8.6.7. Local Scope¶
Variables defined inside function
Variables are not available from outside
If outside the function, will return the same as
globals()
>>> locals()
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>}
>>> def echo():
... a = 1
... print(locals())
>>>
>>>
>>> echo()
{'a': 1}
>>> def echo(a, b=2):
... c = 3
... print(locals())
>>>
>>>
>>> echo(1)
{'a': 1, 'b': 2, 'c': 3}
If outside the function, will return the same as globals()
:
>>> locals() == globals()
True
8.6.8. Shadowing Global Scope¶
Defining variable with the same name as in outer scope
Shadowed variable will be deleted upon function return
Shadowing of a global scope is used frequently in Mocks and Stubs. This way, we can simulate user input. Note that Mocks and Stubs will stay until the end of a program.
>>> def input(prompt):
... return 'Mark Watney'
>>>
>>>
>>> name = input('Type your name: ')
>>> name
'Mark Watney'
>>>
>>> age = input('Type your age: ')
>>> age
'Mark Watney'
>>> from unittest.mock import MagicMock
>>> input = MagicMock(side_effect=['Mark Watney', '44'])
>>>
>>>
>>> name = input('Type your name: ')
>>> name
'Mark Watney'
>>>
>>> age = input('Type your age: ')
>>> age
'44'
To restore default behavior of input()
function use:
>>> from builtins import input
8.6.9. Builtins¶
>>> import builtins
>>>
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BaseExceptionGroup', 'BlockingIOError', 'BrokenPipeError', 'BufferError',
'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError',
'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError',
'DeprecationWarning', 'EOFError', 'Ellipsis', 'EncodingWarning',
'EnvironmentError', 'Exception', 'ExceptionGroup', 'False',
'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
'MemoryError', 'ModuleNotFoundError', 'NameError', 'None',
'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError',
'OverflowError', 'PendingDeprecationWarning', 'PermissionError',
'ProcessLookupError', 'RecursionError', 'ReferenceError',
'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration',
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
'__debug__', '__doc__', '__import__', '__loader__', '__name__',
'__package__', '__spec__', 'abs', 'aiter', 'all', 'anext', 'any', 'ascii',
'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr',
'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr',
'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter',
'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash',
'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter',
'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min',
'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
'zip']
8.6.10. Use Case - 0x01¶
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'
8.6.11. Use Case - 0x02¶
>>> 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...>
8.6.12. References¶
8.6.13. Assignments¶
"""
* Assignment: Functional Scope Global
* Required: yes
* Complexity: easy
* Lines of code: 5 lines
* Time: 5 min
English:
1. Define in global scope `SELECT: set[str]` with values:
`'setosa', 'versicolor'`
2. Define function `sumif(features, label)`
3. Function sums `features`, only when `label` is in `SELECT`
4. When `label` 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(features, label)`
3. Funkcja sumuje `features`, tylko gdy `label` jest w `SELECT`
4. Gdy `label` nie występuje w `select` zwróć `0` (zero)
5. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> 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(features, label)`'
>>> signature(sumif)
<Signature (features, label)>
>>> 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 features, when label is in SELECT, else return 0 (zero)
# type: Callable[[list[float], str], float]
def sumif():
...