📦 Репозиторий занятия 38

Урок 38. Функции. Области видимости

Как работать с репозиторием

Каждая карточка говорит, о чём файл, что он выводит и что в нём искать. Код виден прямо здесь: его можно скопировать одной кнопкой, скачать файл или открыть его целиком.

Маршрут изучения

  1. Прочитайте описание: по нему уже понятно, о чём файл и что он выведет.
  2. Предскажите вывод: сравните своё предположение со строкой «Что выводит».
  3. Запустите: скачайте файл или скопируйте код кнопкой и выполните его у себя.
  4. Измените: поменяйте одно условие или значение и объясните новый результат.

Файлы: рекомендуемый порядок

1
MarkdownРазбор концепции116 строк

Функция в Python: синтаксис, return, правила PEP 8

less_20__function__scope__args_kwargs/theory_01__function_definition.md

Базовое определение функции: синтаксис def/docstring/return, объяснение того, что функция без явного return всё равно возвращает None, пример с несколькими return вместо if/else, и то, что return не может вернуть больше одного значения напрямую — только собрав их в коллекцию. Завершается перечнем правил PEP 8 для оформления функций.

  • def function_name(parameters): ... return — базовый синтаксис определения и вызова
  • greet(name) — простая функция без возврата значения
  • add(a, b) — функция с return
  • is_zero_or_positive(num) — несколько return вместо if/else
  • get_all_even_numbers(*args) — 'несколько результатов' через список
  • Правила PEP 8: snake_case, две пустые строки после функции, пробелы у параметров
Показать начало файла (116 строк всего)
## Функция в Python.

### Определение 

**Функция** — это именованный блок кода, который 
- выполняет определённое действие 
- и может быть вызван в программе многократно.

Функции позволяют 
- избегать дублирования кода (лаконичность и читабельность)
- структурировать код, разбить его на независимые модули, которые 
  - удобно по одному отладить по одному
  - и затем объединить в единое целое ("разделяй и властвуй")


### Синтаксис

```python
# Определение функции

def function_name(parameters):
    """docstring описание функции""" # (optional)
    ...
    # function_body
    ...
    expression = ...
    return expression  # (optional)
    
    
# Вызов функции
arguments = ...
function_name(arguments)
```


* `def` — ключевое слово, используется для определения функции 
* `function_name` — имя функции (`snake_case`, обычно глагол как действие)
* `parameters` — (optional) значения, которые функция может принимать при вызове
* `"""docstring"""` — (optional) краткое описание того, что функция делает
* `function_body` — тело функции (исполняемый функцией код)
* `return` — (optional) оператор как часть тела функции. Возвращает значение в точку вызова функции
* ВАЖНО: функция, которая ничего не возвращает, всё равно возвращает `None`


```python
def greet(name):
    """Prints a greeting with the given name."""
    print(f"Hello, {name}!")

greet("John")
# Hello, John!
```


Если функция должна **вернуть результат**, используется ключевое слово **`return`**:

```python
def add(a, b):
    return a + b

…
Проверьте себя: Функция без явного return возвращает None — а что вернёт функция, у которой return стоит, но без выражения после него (просто return)?
2
MarkdownРазбор концепции195 строк

Packing и unpacking: *args, **kwargs, / и *

less_20__function__scope__args_kwargs/theory_02__packing__unpacking__in_funcs.md

Разбирает packing (*args, **kwargs в определении функции — сбор аргументов в tuple/dict) и unpacking (*, ** при вызове — распаковка последовательности/словаря в отдельные аргументы). Отдельно — строгий порядок параметров в определении (позиционные → *args → именованные с default → **kwargs) и синтаксис Python 3.8+ для принудительно позиционных (/) и принудительно именованных (*) аргументов.

  • show_numbers(*args) — все позиционные аргументы упаковываются в tuple
  • show_info(**kwargs) — все именованные аргументы упаковываются в dict
  • add(*nums) и show_person(**person) — распаковка последовательности и словаря при вызове
  • display(*args, **kwargs) — совместное использование, *args обязательно перед **kwargs
  • example(a, b, *args, option=True, **kwargs) — полный порядок параметров
  • example(a, b, /, c, *, d) — / и * ограничивают вид передаваемых аргументов
Показать начало файла (195 строк всего)
# Packing и Unpacking 

## 1. Packing (упаковка аргументов)

**Packing** — это сбор нескольких аргументов в один объект (`tuple` или `dict`) при передаче в функцию.
Используется операторы `*` и `**` в **определении функции**.

---

### `*args` — упаковка позиционных аргументов

```python
def show_numbers(*args):
    print(args)
    print(type(args))

show_numbers(1, 2, 3)
# (1, 2, 3)
<class 'tuple'>
```


---

### `**kwargs` — упаковка именованных аргументов

```python
def show_info(**kwargs):
    print(kwargs)

    
show_info(name="Alice", age=25, city="Paris")
# {'name': 'Alice', 'age': 25, 'city': 'Paris'}
```

---

## 2. Unpacking (распаковка аргументов)

**Unpacking** — это передача элементов последовательности или словаря в функцию **по отдельности**  
с помощью операторов `*` и `**` в **вызове функции**.

---

### Распаковка списка или тюпла

```python
def add(a, b, c):
    return a + b + c

nums = (1, 2, 3)
print(add(*nums))  # эквивалентно add(1, 2, 3)
```


### Распаковка словаря

```python
def show_person(name, age, city):
    print(name, age, city)
…
Проверьте себя: Почему sum_all(a=1, b=2) даёт ошибку для функции def sum_all(*args), хотя sum_all(1, 2) работает нормально?
3
PythonИсполняемый пример11 строк

UnboundLocalError: переменная 'не существует' до присваивания внутри функции

less_20__function__scope__args_kwargs/theory_03_scope.py

Демонстрирует классическую ловушку области видимости: внутри main() выполняется count += 1, но раз count где-то присваивается в теле функции, Python на этапе компиляции считает её локальной переменной этой функции целиком — и падает при попытке прочитать её до первого присваивания. Наличие одноимённой глобальной переменной не спасает: без global функция её не видит.

  • count = 0 — глобальная переменная
  • def main(): count += 1 — попытка изменить count, считая её локальной
  • print(...) до main() — глобальный count виден и равен 0
  • main() — падает с UnboundLocalError на строке count += 1

Что выводит: before function main 0 Traceback (most recent call last): ... count += 1 ^^^^^ UnboundLocalError: cannot access local variable 'count' where it is not associated with a value (файл завершается с ошибкой, до строки print("after function main", count) выполнение не доходит)

Файл целиком (11 строк)
count = 0


def main():
    count += 1
    print("inside the main function", count)


print("before function main", count)
main()
print("after function main", count)
Проверьте себя: Что нужно добавить в тело main(), чтобы count += 1 работало и модифицировало именно глобальную переменную?
Открыть файл →
4
PythonИсполняемый пример18 строк

Три независимых count: global, outer, inner (LEGB на практике)

less_20__function__scope__args_kwargs/theory_04_scope.py

outer() и вложенная в неё inner() создают собственные локальные переменные count, никак не связанные ни с глобальной, ни друг с другом; print с id(count) наглядно показывает, что это три разных объекта в памяти. После возврата из inner(), а затем из outer(), внешние count остаются нетронутыми — каждая область видит только свою переменную.

  • count = 0 — глобальная переменная, свой id
  • outer(): count = 1 — локальная переменная outer, другой id
  • inner(): count = 2 — локальная переменная inner, ещё один id
  • После вызова inner() outer снова видит СВОЙ count = 1, а не 2
  • После outer() глобальный count остаётся 0

Что выводит: before the outer function 0 <id_global> inside the outer function before the inner function 1 <id_outer> inside the inner function 2 <id_inner> inside the outer function after the inner function 1 <id_outer> after the outer function 0 <id_global> (конкретные числовые id зависят от запуска, но все три различны)

Файл целиком (18 строк)
count = 0


def outer():
    count = 1
    print("inside the outer function before the inner function", count, id(count))

    def inner():
        count = 2
        print("inside the inner function", count, id(count))

    inner()
    print("inside the outer function after the inner function", count, id(count))


print("before the outer function", count, id(count))
outer()
print("after the outer function", count, id(count))
Проверьте себя: Если убрать строку count = 2 внутри inner(), что inner() выведет вместо 2, и откуда возьмётся это значение?
Открыть файл →
5
PythonИсполняемый пример18 строк

Enclosing scope: inner() читает count из outer(), когда своей переменной нет

less_20__function__scope__args_kwargs/theory_05_scope.py

Продолжение предыдущего примера — строка count = 2 внутри inner() закомментирована. При поиске имени count Python поднимается по правилу LEGB: локальной переменной в inner() нет, поэтому он переходит в enclosing-область и находит count = 1, объявленный в outer(). Наглядная демонстрация того, что enclosing scope — это именно область объемлющей функции, а не глобальная.

  • count = 0 — глобальная переменная
  • outer(): count = 1 — enclosing-переменная для inner()
  • inner(): своей count нет → читает count = 1 из outer() (enclosing scope)
  • После вызова inner() outer() по-прежнему видит count = 1
  • Глобальный count остаётся 0 на всём протяжении

Что выводит: before the outer function 0 inside the outer function before the inner function 1 inside the inner function 1 inside the outer function after the inner function 1 after the outer function 0

Файл целиком (18 строк)
count = 0


def outer():
    count = 1
    print("inside the outer function before the inner function", count)

    def inner():
        # count = 2
        print("inside the inner function", count)

    inner()
    print("inside the outer function after the inner function", count)


print("before the outer function", count)
outer()
print("after the outer function", count)
Проверьте себя: Почему inner() увидела count = 1 из outer(), а не count = 0 из глобальной области — ведь обе переменные существуют одновременно?
Открыть файл →
6
PythonИсполняемый пример18 строк

LEGB до конца: outer() и inner() оба читают глобальный count

less_20__function__scope__args_kwargs/theory_06_scope.py

Третий шаг серии: теперь обе локальные строки (count = 1 в outer и count = 2 в inner) закомментированы. Ни outer, ни inner не имеют собственной или enclosing-переменной count, поэтому поиск по правилу LEGB доходит до глобальной области видимости, и обе функции печатают глобальный count = 0.

  • count = 0 — единственная реально существующая переменная count в программе
  • outer(): своей count нет → берёт глобальную
  • inner(): своей count нет, и в enclosing (outer) её тоже нет → берёт глобальную
  • Все четыре print() внутри функций и снаружи выводят одно и то же значение 0

Что выводит: before the outer function 0 inside the outer function before the inner function 0 inside the inner function 0 inside the outer function after the inner function 0 after the outer function 0

Файл целиком (18 строк)
count = 0


def outer():
    # count = 1
    print("inside the outer function before the inner function", count)

    def inner():
        # count = 2
        print("inside the inner function", count)

    inner()
    print("inside the outer function after the inner function", count)


print("before the outer function", count)
outer()
print("after the outer function", count)
Проверьте себя: Если внутри inner() дописать count += 1 (без nonlocal/global), к какой ошибке это приведёт и почему, в отличие от простого чтения count, так уже не сработает?
Открыть файл →
7
PythonИсполняемый пример12 строк

Локальная переменная невидима снаружи функции

less_20__function__scope__args_kwargs/theory_07_scope_invisible_local.py

main() создаёт локальную count_main = 1 и попутно читает глобальную count — обе операции внутри функции работают без проблем. Но после возврата из main() имя count_main в глобальной области не существует: попытка обратиться к нему снаружи падает с NameError. Файл наглядно показывает границу локальной области видимости — локальные имена не 'утекают' наружу функции.

  • count = 0 — глобальная переменная, видна и внутри, и снаружи main()
  • main(): count_main = 1 — локальная переменная, читает также глобальный count
  • print(...) после main() — глобальный count по-прежнему доступен
  • print(count_main) после main() — падает, count_main не существует за пределами функции

Что выводит: count inside the main function 0 count_main inside the main function 1 count outside the main function 0 Traceback (most recent call last): ... print("count_main outside the main function", count_main) ^^^^^^^^^^ NameError: name 'count_main' is not defined

Файл целиком (12 строк)
count = 0  # global


def main():
    count_main = 1  # local
    print("count inside the main function", count)
    print("count_main inside the main function", count_main)


main()
print("count outside the main function", count)
print("count_main outside the main function", count_main)
Проверьте себя: Почему count доступен и внутри, и снаружи main(), а count_main — только внутри, хотя обе переменные создаются похожим образом?
Открыть файл →
8
PythonИсполняемый пример12 строк

global: как функция меняет переменную во внешней области

less_20__function__scope__args_kwargs/theory_08_scope_global.py

Показывает ключевое слово global, которое явно указывает Python не создавать локальную переменную, а работать с той, что объявлена на уровне модуля. Благодаря global count внутри main() строка count += 1 реально увеличивает глобальную переменную, и это изменение видно после вызова функции — в отличие от theory_03_scope.py, где тот же самый код без global падал с UnboundLocalError.

  • count = 0 — глобальная переменная
  • def main(): global count — явная привязка имени к глобальной области
  • count += 1 — изменяет именно глобальную переменную, а не создаёт локальную
  • print() после main() показывает уже изменённое значение 1

Что выводит: before function main 0 inside the main function 1 after function main 1

Файл целиком (12 строк)
count = 0


def main():
    global count
    count += 1
    print("inside the main function", count)


print("before function main", count)
main()
print("after function main", count)
Проверьте себя: Чем этот файл отличается по коду от theory_03_scope.py, и почему там без global происходит UnboundLocalError, а здесь всё работает?
Открыть файл →
9
PythonИсполняемый пример19 строк

nonlocal ломается, если внешняя переменная ещё не инициализирована

less_20__function__scope__args_kwargs/theory_09_scope_nonlocal.py

Файл задуман как демонстрация nonlocal: inner() должна менять count из объемлющей функции outer(). Но при реальном запуске он падает раньше, чем доходит до этой идеи — раз count присваивается внутри outer() (count = 10), Python на этапе компиляции считает count локальной переменной ВСЕЙ функции outer(), включая самую первую строку print(count), которая идёт до этого присваивания. В результате получается тот же UnboundLocalError, что и в theory_03_scope.py, только на уровне enclosing, а не global.

  • count = 0 — глобальная переменная, внутри outer() фактически не используется
  • def outer(): count = 10 — делает count локальной переменной для ВСЕЙ функции outer()
  • print(count) ДО строки count = 10 — падает: локальная count ещё не создана
  • def inner(): nonlocal count — задумана как изменение count из outer(), но outer() уже не доходит до этой строки

Что выводит: 0 Traceback (most recent call last): ... print("inside the outer function before the inner function", count) ^^^^^ UnboundLocalError: cannot access local variable 'count' where it is not associated with a value (печатается только первый print(count) вне функций; outer() падает на своей первой строке, до inner() дело не доходит)

Начало файла
count = 0


def outer():
    print("inside the outer function before the inner function", count)
    count = 10  # Переменная в охватывающей Enclosing (но не глобальной) области видимости
    print("inside the outer function before the inner function", count)

    def inner():
        nonlocal count  # Указываем, что x берется из внешней функции
        count = 20  # Меняем значение x в outer

    inner()
    print(count)  # 20
Показать файл целиком (19 строк)
count = 0


def outer():
    print("inside the outer function before the inner function", count)
    count = 10  # Переменная в охватывающей Enclosing (но не глобальной) области видимости
    print("inside the outer function before the inner function", count)

    def inner():
        nonlocal count  # Указываем, что x берется из внешней функции
        count = 20  # Меняем значение x в outer

    inner()
    print(count)  # 20


print(count)
outer()
print(count)
Проверьте себя: Что нужно изменить в порядке строк функции outer(), чтобы файл действительно продемонстрировал nonlocal и напечатал 20 в конце, как предполагает комментарий в коде?
Открыть файл →
10
MarkdownРазбор концепции61 строк

LEGB: сводка правила поиска имён

less_20__function__scope__args_kwargs/theory_10__Local_Enclosing_Global_Built_in.md

Итоговый обзор четырёх областей видимости — Local, Enclosing, Global, Built-in — с минимальным примером кода на каждую и явной формулировкой порядка поиска имени (LEGB), которым руководствуется интерпретатор. Хорошо читается после серии практических примеров theory_03–theory_09, которые как раз иллюстрируют этот порядок через конкретные удачные случаи и ошибки.

  • Local — переменная x внутри test(), доступна только там
  • Enclosing — x из outer() видна внутри inner(), но не снаружи outer()
  • Global — z объявлена на уровне модуля, видна во всём файле
  • Built-in — print, len и другие встроенные имена
  • Итоговый порядок поиска имени: Local → Enclosing → Global → Built-in
Показать начало файла (61 строк всего)
### 4 уровня видимости: Local, Enclosing, Global, Built-in (LEGB)

В Python обычно выделяют 4 области видимости (scope), где могут находиться переменные:

#### 1. Local (локальная область видимости)

Переменные, созданные внутри функции. 
Доступны только внутри этой функции.

```python
def test():
   x = 10  # локальная переменная
   print(x)
```

#### 2. Enclosing scope (внешняя / `nonlocal` область видимости)

**Enclosing scope** — это область видимости внешней функции целиком для вложенной функции,  
кроме её собственной локальной области.

Для `Enclosing` обязательно существование ДВУХ функция
* внешней (`outer()`)
* и вложенной в неё внутренней функции (`inner()`)


```python
def outer():
    x = 10        # enclosing scope для inner
    print(x)      # enclosing scope для inner 
    def inner():
        y = 5  # local scope
        print(x)  # поиск идёт во внешнюю функцию

    inner()  # enclosing scope для inner
```

#### 3. Global scope (глобальная область видимости)
Переменные, объявленные на уровне модуля.
Доступны во всём файле.

```python
z = 30  # глобальная переменная

def show():
   print(z)
```

#### 4. Built-in scope (встроенная область видимости)

Имена, встроенные в Python: `print`, `len`, `range` и др.

```python
print(len("Python"))
```

Порядок поиска переменных называется правилом **LEGB**:

* **L** — Local
* **E** — Enclosing
* **G** — Global
…
Проверьте себя: В какую область видимости — Local, Enclosing или Global — попадёт переменная, объявленная внутри inner(), если у неё нет одноимённой переменной ни в outer(), ни на уровне модуля?