8.5. Function Scope

8.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

8.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]

8.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]

8.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]

8.5.5. Pure Function

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

8.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

8.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

8.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'}

8.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}

8.5.10. Assignments

Code 8.13. Solution
"""
* Assignment: Function Scope Global
* Filename: function_scope_global.py
* Complexity: easy
* Lines of code: 5 lines
* Time: 8 min

English:
    1. Use data from "Given" section (see below)
    2. Define in global scope `SELECT: set[str]` with values: `'setosa', 'versicolor'`
    3. Define function `sumif(features, label)`
    4. Function sums `features`, only when `label` is in `SELECT`
    5. When `label` is not in `select` return `0` (zero)
    6. Compare result with "Tests" section (see below)

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Zdefiniuj w przestrzeni globalnej `SELECT: set[str]` z wartościami: `'setosa', 'versicolor'`
    3. Zdefiniuj funkcję `sumif(features, label)`
    4. Funkcja sumuje `features`, tylko gdy `label` jest w `SELECT`
    5. Gdy `label` nie występuje w `select` zwróć `0` (zero)
    6. Porównaj wyniki z sekcją "Tests" (patrz poniżej)

Tests:
    >>> from inspect import isfunction
    >>> isfunction(sumif)
    True
    >>> sum(sumif(X,y) for *X, y in DATA[1:])
    49.1
"""


# Given
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')]


Code 8.14. Solution
"""
* Assignment: Function Scope Roman to Int
* Filename: function_scope_romanint.py
* Complexity: hard
* Lines of code: 15 lines
* Time: 21 min

English:
    1. Use data from "Given" section (see below)
    2. Define function converting roman numerals to integer

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Zdefiniuj funkcję przeliczającą liczbę rzymską na całkowitą

Tests:
    >>> from inspect import isfunction
    >>> isfunction(roman_to_int)
    True
    >>> int_to_roman(1)
    'I'
    >>> int_to_roman(9)
    'IX'
    >>> int_to_roman(1550)
    'MDL'
    >>> int_to_roman(1540)
    'MXDL'
    >>> int_to_roman(14)
    'XIV'
"""


# Given
CONVERSION = {
     1: 'I',
     2: 'II',
     3: 'III',
     4: 'IV',
     5: 'V',
     6: 'VI',
     7: 'VII',
     8: 'VIII',
     9: 'IX',
     10: 'X',
     20: 'XX',
     30: 'XXX',
     40: 'XL',
     50: 'L',
     60: 'LX',
     70: 'LXX',
     80: 'LXXX',
     90: 'XC',
     100: 'C',
     500: 'D',
     1000: 'M'}


def int_to_roman(number):
    ...


Code 8.15. Solution
""""
* Assignment: Function Scope Int To Roman
* Filename: function_scope_introman.py
* Complexity: hard
* Lines of code: 13 lines
* Time: 21 min

English:
    1. Use data from "Given" section (see below)
    2. Define function converting integer to roman numerals

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Zdefiniuj funkcję przeliczającą liczbę całkowitą na rzymską

Tests:
    >>> from inspect import isfunction
    >>> isfunction(roman_to_int)
    True
    >>> roman_to_int('I')
    1
    >>> roman_to_int('IX')
    9
    >>> roman_to_int('MDL')
    1550
    >>> roman_to_int('MXDL')
    1540
    >>> roman_to_int('XIV')
    14
"""


# Given
ROMAN = {
    'I': 1,
    'II': 2,
    'III': 3,
    'IV': 4,
    'V': 5,
    'VI': 6,
    'VII': 7,
    'VIII': 8,
    'IX': 9,
    'X': 10,
    'XX': 20,
    'XXX': 30,
    'XL': 40,
    'L': 50,
    'LX': 60,
    'LXX': 70,
    'LXXX': 80,
    'XC': 90,
    'C': 100,
    'D': 500,
    'M': 1000,
}


def roman_to_int(roman: str) -> int:
    ...