12.5. Function Scope

12.5.1. Rationale

  • Functions has access to global values

>>> def add(a, b=1):
...     c = 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
>>>
>>> add(1)
>>>
>>> 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

12.5.2. Outer Scope

  • Functions has access to global values

>>> data = [1, 2, 3]
>>>
>>>
>>> def add():
...     return sum(data)
>>>
>>>
>>> add()
6
>>> print(data)
[1, 2, 3]

12.5.3. Shadowing

>>> data = [1, 2, 3]
>>>
>>>
>>> def add():
...     data = [10, 20, 30]  # Shadows name 'data' from outer scope
...     return sum(data)
>>>
>>>
>>> add()
60
>>> print(data)
[1, 2, 3]

12.5.4. Global

  • Allows modification of global variable

  • BAD PRACTICE!!

>>> data = [1, 2, 3]
>>>
>>>
>>> def add():
...     global data
...     data = [10, 20, 30]
...     return sum(data)
>>>
>>>
>>> add()
60
>>> print(data)
[10, 20, 30]

12.5.5. Pure Function

>>> def add(a, b):
...     return a + b
>>>
>>>
>>> add(1, 2)
3
>>> add(1, 2)
3
>>> add(1, 2)
3

12.5.6. Impure Function

>>> c = 3
>>>
>>>
>>> def add(a, b):
...     return a + b + c
>>>
>>>
>>> add(1, 2)
6
>>> add(1, 2)
6
>>> add(1, 2)
6
>>>
>>> c = 4
>>>
>>> add(1, 2)
7
>>> add(1, 2)
7
>>> add(1, 2)
7

12.5.7. Impure to Pure Function

>>> c = 3
>>>
>>>
>>> def add(a, b, c):
...     return a + b + c
>>>
>>>
>>> add(1, 2, c)
6
>>> add(1, 2, c)
6
>>> add(1, 2, c)
6
>>>
>>> c = 4
>>>
>>> add(1, 2, c)
7
>>> add(1, 2, c)
7
>>> add(1, 2, c)
7

12.5.8. Global Scope

>>> # doctest: +SKIP
... globals()
{'__name__': '__main__',
 '__doc__': None,
 '__package__': None,
 '__loader__': <class '_frozen_importlib.BuiltinImporter'>,
 '__spec__': None,
 '__annotations__': {},
 '__builtins__': <module 'builtins' (built-in)>}
>>> # doctest: +SKIP
... dir(globals()['__builtins__'])
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', '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', 'all', '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']
>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>> # doctest: +SKIP
... globals()
{'__name__': '__main__',
 '__doc__': None,
 '__package__': None,
 '__loader__': <class '_frozen_importlib.BuiltinImporter'>,
 '__spec__': None,
 '__annotations__': {},
 '__builtins__': <module 'builtins' (built-in)>,
 'firstname': 'Mark',
 'lastname': 'Watney'}

12.5.9. Local Scope

  • Variables defined inside function

  • Variables are not available from outside

  • If outside the function, will return the same as globals()

>>> # doctest: +SKIP
... 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}

12.5.10. Shadowing Global Scope

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'

To restore default behavior of input() function use:

>>> input = __builtins__['input']

12.5.11. Assignments

Code 12.12. Solution
"""
* Assignment: Function Scope Global
* Required: yes
* Complexity: easy
* Lines of code: 5 lines
* Time: 8 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

    >>> isfunction(sumif)
    True
    >>> 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'),
]