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

Урок 64. Декораторы

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

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

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

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

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

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

Области видимости LEGB

less_32__closure_decorator/theory_01__scope.md

Вводит правило LEGB (Local, Enclosing, Global, Built-in), по которому Python ищет имя переменной. На простом примере с тремя вложенными уровнями (глобальная x, x внутри outer, x внутри inner) показано, что каждый уровень видит свою собственную x, не затрагивая остальные, а поиск идёт строго изнутри наружу.

  • L — Local: локальная область функции
  • E — Enclosing: область внешних функций для вложенных
  • G — Global: область модуля
  • B — Built-in: встроенные имена (len, print, range)
  • Пример: три независимых x на трёх уровнях + len(x) на глобальном уровне
Показать начало файла (55 строк всего)
## Области видимости в Python (LEGB)

Python использует правило **LEGB**, которое определяет порядок поиска переменной:

1. **L — Local**
   Локальная область внутри функции или лямбда-выражения.

2. **E — Enclosing**
   Область всех внешних функций (для вложенных функций).

3. **G — Global**
   Область модуля (файл Python).

4. **B — Built-in**
   Встроенные имена Python (например, `len`, `print`, `range`).

---

### Простой пример LEGB

```python
x = "global"

def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print('x from local:', x)

    inner()
    
    print('x from outer:', x)

outer()

print('x from global:', x)

print('len(x) =', len(x))
```

Вывод:

```
x from local: local
x from outer: enclosing
x from global: global
len(x) = 6

```

### Вывод:

Python ищет `x` в порядке: **Local → Enclosing → Global → Built-in**.
Проверьте себя: В каком порядке Python ищет имя переменной, и что произойдёт, если удалить строку `x = "local"` внутри inner()?
2
MarkdownРазбор концепции128 строк

Enclosing scope и рождение замыкания

less_32__closure_decorator/theory_02__Enclosing_scope.md

Подробно раскрывает Enclosing scope как ближайшую лексическую область вокруг внутренней функции — то есть всё содержимое внешней функции, кроме собственных локальных имён внутренней. Показывает, что enclosing-переменные доступны внутренней функции только на чтение, а для изменения нужен nonlocal; на примере make_adder(a) объясняется, как эта модель превращается в замыкание — переменная a «запоминается» внутри adder даже после завершения make_adder.

  • Определение: enclosing — лексическая область вокруг inner(), без её локальных имён
  • Доступ по умолчанию — только чтение; присваивание создаёт новую локальную переменную
  • nonlocal x — способ изменить enclosing-переменную из inner()
  • make_adder(a) → adder(b): a запоминается в замыкании
  • Таблица Enclosing vs Global
  • Итог: замыкание — редкий объект Python, хранящий одновременно данные и код
Показать начало файла (128 строк всего)


## Что такое `Enclosing` в Python?

**Enclosing scope** — это один из уровней областей видимости в Python,  
возникающий, когда функция определена *внутри другой функции*.

## Определение `enclosing scope`

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

1. Понятие **enclosing** всегда определяется относительно **внутренней** функции,  
а не как самостоятельная сущность **внешней** функции.

2. **Лексическая область видимости (`lexical scope`)** в определении означает, что данная  
область видимости определяется тем, **где в коде** написана функция или переменная,  
а не тем, где и когда она вызывается.

Иными словами, `enclosing scope` для внутренней функции `inner()`, будет  
ВСЯ область внешней функции `outer(*args, **kwargs)`, включая параметры `*args, **kwargs`.  
⚠️ НО, **за исключением** локальных переменных самой функции`inner()`.

---

## Иерархия областей видимости в Python

Как мы помним, иерархия областей видимости в Python обозначается аббревиатурой **LEGB**:

1. **L – Local** (локальная область функции)
2. **E – Enclosing** (область внешней функции, окружающая)
3. **G – Global** (глобальная область модуля)
4. **B – Built-in** (встроенные имена Python)

---

## Когда появляется Enclosing?

Когда одна функция определена внутри другой:

```python
def outer():
    x = 10
    
    def inner():
        print(x)  # x из enclosing scope функции outer
    
    inner()
```

Здесь:

* `x` — **enclosing variable**
* `outer` — *enclosing function*
* `inner` — *inner function*, которая имеет доступ к окружению `outer`

---

## Свойства Enclosing scope

…
Проверьте себя: Почему в примере с `x = 20` внутри inner() без nonlocal итоговый print(x) в outer() всё равно печатает 10?
3
PythonИсполняемый пример60 строк

Три практических примера замыканий

less_32__closure_decorator/theory_03__closures_examples.py

Три самостоятельных сценария использования замыканий: фабрика умножителей multiplier(a), которая через enclosing-переменную a создаёт готовые функции multiply_by_2/multiply_by_3; счётчик запусков counter(), использующий nonlocal count для накопления состояния между вызовами; и memoriser(), сохраняющий параметры всех вызовов в списке params, доступном через замыкание.

  • multiplier(a) → inner(b): return a * b — фабрика функций-умножителей
  • counter(count=0) → inner(): nonlocal count — счётчик вызовов с состоянием
  • memoriser() → inner(*args): params.append(...) — накопление истории вызовов
  • add_to_list() без аргументов — извлечение накопленных params

Что выводит: 10, затем 15 (умножители на 2 и 3); затем три строки «Функция запускается в N-й раз» с N=1,2,3; затем две строки '1, 2, 3' и 'abc' — сохранённые параметры вызовов.

Начало файла
"""С помощью замыкания создаём функцию с нужными множителем"""

def multiplier(a):
    def inner(b):
        return a * b
    return inner


multiply_by_2 = multiplier(2)
multiply_by_3 = multiplier(3)

print(multiply_by_2(5))
print(multiply_by_3(5))
Показать файл целиком (60 строк)
"""С помощью замыкания создаём функцию с нужными множителем"""

def multiplier(a):
    def inner(b):
        return a * b
    return inner


multiply_by_2 = multiplier(2)
multiply_by_3 = multiplier(3)

print(multiply_by_2(5))
print(multiply_by_3(5))

# 10
# 15

""" ============= Счётчик запуска функций =============="""

def counter(count=0):
    def inner():
        nonlocal count
        count += 1
        print(f"Функция запускается в {count}-й раз")
    return inner


run_counter = counter()

run_counter()
run_counter()
run_counter()

# Функция запускается в 1-й раз
# Функция запускается в 2-й раз
# Функция запускается в 3-й раз


""" ======= Сохранение параметров функций в списке ========"""

def memoriser():
    params = []
    def inner(*args):
        params.append(', '.join(map(str, args)))
        return params
    return inner


add_to_list = memoriser()

# Передача параметров в функцию и далее в список параметров params
add_to_list(1, 2, 3)
add_to_list('abc')

# Извлечение сохранённых параметров
for params in add_to_list():
    print(params)

# 1, 2, 3
# abc
Проверьте себя: Почему run_counter() продолжает увеличивать count при каждом новом вызове, хотя переменная count объявлена внутри counter(), а не в глобальной области?
Открыть файл →
4
PythonИсполняемый пример40 строк

Что такое декоратор — и первая неудачная попытка его написать

less_32__closure_decorator/theory_04_what_is_decorator.py

Даёт определение декоратора как callable-объекта, который принимает функцию, дополняет её поведение и возвращает callable, не изменяя исходную функцию. Перечисляет 4 вида декораторов, но фокусируется только на @func for function. Практический пример намеренно показывает НЕПРАВИЛЬНУЮ реализацию: decorator(func) сразу вызывает func() вместо того чтобы вернуть новую функцию — из-за этого decorator(some_func) возвращает None, а не готовый к отложенному вызову callable.

  • Определение декоратора и правило «дополняет, а не изменяет»
  • 4 вида декораторов, разбирается только @func for function
  • decorator(func): печать рамки + немедленный вызов func()
  • Недостаток: decorator(some_func) исполняется сразу, а не возвращает функцию
  • print(type(decorator(some_func))) — <class 'NoneType'>

Что выводит: Рамка из 50 звёздочек, 'Something useful...', рамка из звёздочек — дважды подряд (вызов decorator(some_func) внутри print(type(...)) снова исполняет функцию), затем <class 'NoneType'>.

Начало файла
"""ДЕКОРАТОР - это callable-объект, который принимают
другой callable объект (функция или метод) в качестве аргумента,
добавляют к нему некоторую функциональность
и возвращают callable-объект с дополненной функциональностью.

ВАЖНО: декоратор НЕ ИЗМЕНЯЕТ исходный callable-объект, а ДОПОЛНЯЕТ его.

Существуют 4 вида декораторов:
1. @func for function
2. @func for class
3. @class for function
4. @class for class

Мы попробуем рассмотреть только первый вид: @func for function
Показать файл целиком (40 строк)
"""ДЕКОРАТОР - это callable-объект, который принимают
другой callable объект (функция или метод) в качестве аргумента,
добавляют к нему некоторую функциональность
и возвращают callable-объект с дополненной функциональностью.

ВАЖНО: декоратор НЕ ИЗМЕНЯЕТ исходный callable-объект, а ДОПОЛНЯЕТ его.

Существуют 4 вида декораторов:
1. @func for function
2. @func for class
3. @class for function
4. @class for class

Мы попробуем рассмотреть только первый вид: @func for function
"""


def some_func():
    print('Something useful...')


# Предположим, у нас есть НЕСКОЛЬКО функций, до и после которых
# надо вывести *************************************************


def decorator(func):
    print(50 * '*')
    func()
    print(50 * '*')


decorator(some_func)

# НЕДОСТАТОК:
# этот декоратор сразу же ИСПОЛНЯЕТ функцию some_func,
# а должен её ВОЗВРАЩАТЬ, чтобы можно было бы выполнить её
# в любой удобный момент, а не прямо сейчас.

print(type(decorator(some_func)))  # <class 'NoneType'>
# то есть возвращает None, а должен возвращать Callable object
Проверьте себя: Почему decorator(some_func) выполняется дважды при запуске файла, хотя в коде вызов decorator(some_func) записан только один раз явно плюс один раз внутри print?
Открыть файл →
5
PythonИсполняемый пример24 строк

Исправленный декоратор: возвращаем wrapper вместо немедленного вызова

less_32__closure_decorator/theory_05__simple_decorator.py

Исправляет проблему предыдущего файла: decorator(func) теперь определяет и возвращает внутреннюю функцию wrapper(), которая при вызове печатает рамку и вызывает func(). Это делает decorator(some_func) настоящим Callable-объектом (проверено isinstance), который нужно вызывать дополнительной парой скобок — decorator(some_func)().

  • def decorator(func): def wrapper(): ...; return wrapper
  • isinstance(decorator(some_func), Callable) — проверка, что теперь это функция
  • decorator(some_func)() — двойной вызов: получить wrapper, затем выполнить его

Что выводит: True (decorator(some_func) — Callable), затем рамка из звёздочек, 'Something useful...', рамка из звёздочек.

Начало файла
from typing import Callable


def some_func():
    print('Something useful...')


def decorator(func):
    def wrapper():
        print(50 * '*')
        func()
        print(50 * '*')
    return wrapper
Показать файл целиком (24 строк)
from typing import Callable


def some_func():
    print('Something useful...')


def decorator(func):
    def wrapper():
        print(50 * '*')
        func()
        print(50 * '*')
    return wrapper


# Теперь функция decorator действительно - декоратор!
print(isinstance(decorator(some_func), Callable))  # True

# Поэтому для её вызова нужно добавить скобки:
decorator(some_func)()

# **************************************************
# Something useful...
# **************************************************
Проверьте себя: Почему теперь для запуска обёрнутой функции нужны вторые скобки — decorator(some_func)(), а не просто decorator(some_func)?
Открыть файл →
6
PythonИсполняемый пример19 строк

Синтаксис @decorator вместо ручной обёртки

less_32__closure_decorator/theory_06_classic_decorators_form.py

Показывает, что запись @decorator над def some_func() — это просто удобный синтаксис для some_func = decorator(some_func). После применения декоратора вызов some_func() уже не требует дополнительных скобок для получения wrapper — сам декоратор применяется автоматически при определении функции.

  • @decorator над def some_func() — синтаксический сахар
  • some_func() — вызывается напрямую, без второй пары скобок

Что выводит: Рамка из 50 звёздочек, 'Something useful...', рамка из 50 звёздочек.

Начало файла
def decorator(func):
    def wrapper():
        print(50 * '*')
        func()
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func():
    print(f'Something useful...')

Показать файл целиком (19 строк)
def decorator(func):
    def wrapper():
        print(50 * '*')
        func()
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func():
    print(f'Something useful...')


some_func()

# **************************************************
# Something useful...
# **************************************************
Проверьте себя: К какой строке кода эквивалентна запись `@decorator` перед `def some_func():`?
Открыть файл →
7
PythonИсполняемый пример23 строк

Побочный эффект декоратора: подмена __name__ и __doc__

less_32__closure_decorator/theory_07_what_returns_our_decorator.py

Демонстрирует проблему «прозрачности» простого декоратора: после декорирования some_func.__name__ и some_func.__doc__ показывают не 'some_func' и её докстринг, а 'wrapper' и докстринг обёртки — потому что имя some_func на самом деле теперь ссылается на функцию wrapper, а не на исходную.

  • wrapper() имеет собственный docstring "Wrapper's doc_string"
  • @decorator подменяет some_func на wrapper
  • some_func.__name__ внутри самой функции печатает 'wrapper', а не 'some_func'
  • some_func.__doc__ печатает докстринг wrapper, а не исходной функции

Что выводит: Рамка, 'Something useful...', 'The name of our function is ...wrapper', 'The doc_string of our function is ...Wrapper's doc_string', рамка.

Начало файла
def decorator(func):
    def wrapper():
        """Wrapper's doc_string"""
        print(50 * '*')
        func()
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func():
    """Doc_string for some_func"""
    print(f'Something useful...')
Показать файл целиком (23 строк)
def decorator(func):
    def wrapper():
        """Wrapper's doc_string"""
        print(50 * '*')
        func()
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func():
    """Doc_string for some_func"""
    print(f'Something useful...')
    print(f"The name of our function is ...{some_func.__name__}")
    print(f"The doc_string of our function is ...{some_func.__doc__}")


some_func()

# **************************************************
# Something useful...
# **************************************************
Проверьте себя: Почему some_func.__name__ показывает 'wrapper', если сама функция называется some_func и именно её мы вызываем?
Открыть файл →
8
PythonИсполняемый пример23 строк

Универсальный декоратор через *args, **kwargs

less_32__closure_decorator/theory_08_args_kwargs.py

Расширяет wrapper сигнатурой *args, **kwargs, чтобы декоратор одинаково работал с функциями любого числа аргументов — здесь some_func принимает comment. Проблема с __name__/__doc__ из предыдущего файла всё ещё не решена — она станет мотивацией для functools.wraps в следующем файле.

  • def wrapper(*args, **kwargs): ... func(*args, **kwargs)
  • some_func('for me') — аргумент прозрачно проходит внутрь через wrapper
  • __name__ и __doc__ всё ещё показывают 'wrapper', а не 'some_func'

Что выводит: Рамка, 'Something useful...for me', 'The name of our function is ...wrapper', 'The doc_string of our function is ...Wrapper's doc_string', рамка.

Начало файла
def decorator(func):
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        func(*args, **kwargs)
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func(comment):
    """Doc_string for some_func"""
    print(f'Something useful...{comment}')
Показать файл целиком (23 строк)
def decorator(func):
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        func(*args, **kwargs)
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func(comment):
    """Doc_string for some_func"""
    print(f'Something useful...{comment}')
    print(f"The name of our function is ...{some_func.__name__}")
    print(f"The doc_string of our function is ...{some_func.__doc__}")


some_func('for me')

# **************************************************
# Something useful...
# **************************************************
Проверьте себя: Что изменилось в сигнатуре wrapper по сравнению с предыдущим файлом, и почему без этого декоратор не смог бы обернуть some_func(comment)?
Открыть файл →
9
PythonИсполняемый пример27 строк

functools.wraps: сохраняем имя и докстринг оригинальной функции

less_32__closure_decorator/theory_09_wraps.py

Решает проблему подмены метаданных: добавление @wraps(func) над wrapper копирует в него __name__, __doc__ и другие атрибуты исходной функции func. После этого some_func.__name__ и __doc__ снова показывают корректные значения самой some_func, а не wrapper.

  • from functools import wraps
  • @wraps(func) над def wrapper(*args, **kwargs)
  • some_func.__name__ теперь верно показывает 'some_func'
  • some_func.__doc__ верно показывает докстринг some_func

Что выводит: Рамка, 'Something useful...for me', 'The name of our function is ...some_func', 'The doc_string of our function is ...Doc_string for some_func', рамка.

Начало файла
from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        func(*args, **kwargs)
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
Показать файл целиком (27 строк)
from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        func(*args, **kwargs)
        print(50 * '*')
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func(comment):
    """Doc_string for some_func"""
    print(f'Something useful...{comment}')
    print(f"The name of our function is ...{some_func.__name__}")
    print(f"The doc_string of our function is ...{some_func.__doc__}")


some_func('for me')

# **************************************************
# Something useful...
# **************************************************
Проверьте себя: Что именно копирует functools.wraps из func в wrapper, и почему без него отладка декорированного кода была бы сложнее?
Открыть файл →
10
PythonИсполняемый пример29 строк

Декоратор должен возвращать значение исходной функции

less_32__closure_decorator/theory_10_return.py

Ещё одна типичная ошибка декораторов: если wrapper не возвращает результат func(*args, **kwargs), то вызов декорированной функции всегда даёт None, даже если исходная функция что-то возвращает. Файл показывает исправление: сохранить результат в переменную f и вернуть его из wrapper.

  • f = func(*args, **kwargs) внутри wrapper
  • return f — прокидывание результата наружу
  • some_func возвращает 'RETURN FROM some_func _', и это доходит до print()

Что выводит: Рамка, 'Something useful...for me', 'The name of our function is ...some_func', 'The doc_string of our function is ...Doc_string for some_func', рамка, затем 'RETURN FROM some_func _'.

Начало файла
from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        f = func(*args, **kwargs)
        print(50 * '*')
        return f
    return wrapper

Показать файл целиком (29 строк)
from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        f = func(*args, **kwargs)
        print(50 * '*')
        return f
    return wrapper


# Однако декоратор в Python имеет более удобное обозначение
@decorator
def some_func(comment):
    """Doc_string for some_func"""
    print(f'Something useful...{comment}')
    print(f"The name of our function is ...{some_func.__name__}")
    print(f"The doc_string of our function is ...{some_func.__doc__}")
    return 'RETURN FROM some_func _'


print(some_func('for me'))

# **************************************************
# Something useful...
# **************************************************
Проверьте себя: Что напечатает print(some_func('for me')), если убрать `return f` из wrapper и оставить только вызов `func(*args, **kwargs)` без сохранения результата?
Открыть файл →
11
PythonИсполняемый пример18 строк

«Подумаем»: декоратор, меняющий возвращаемое значение

less_32__closure_decorator/theory_11_lets_think!.py

Небольшая задача на понимание: uppercase_decorator оборачивает print_some_text(), которая возвращает 'some_text', и приводит результат к верхнему регистру внутри wrapper. Файл специально называется «lets_think» — приглашение прочитать код и предсказать вывод до запуска.

  • uppercase_decorator(function) → wrapper(): lowercase_text.upper()
  • @uppercase_decorator над print_some_text() -> 'some_text'
  • print(print_some_text()) — вывод результата декорированного вызова

Что выводит: SOME_TEXT

Файл целиком (18 строк)
"""Давайте подумаем: "Что происходит в этом коде"? """


def uppercase_decorator(function):
    def wrapper():
        lowercase_text = function()
        make_uppercase = lowercase_text.upper()
        return make_uppercase

    return wrapper


@uppercase_decorator
def print_some_text():
    return 'some_text'


print(print_some_text())
Проверьте себя: Что вернёт print_some_text() до применения декоратора, и что после — почему?
Открыть файл →
12
PythonИсполняемый пример36 строк

Параметризированный декоратор — декоратор с собственными аргументами

less_32__closure_decorator/theory_12_param_decorator.py

Вводит трёхуровневую конструкцию: param_decorator(parameter) — внешняя функция, принимающая параметр самого декоратора, возвращает decorator(func), который в свою очередь возвращает wrapper. Так @param_decorator(2) над some_func умножает результат оборачиваемой функции на переданный parameter (в виде повторения строки <result> дважды).

  • param_decorator(parameter) → decorator(func) → wrapper(*args, **kwargs)
  • @wraps(func) внутри самого внутреннего уровня для сохранения метаданных
  • return parameter * f"<{result}>" — использование параметра декоратора
  • @param_decorator(2) над some_func('for me')

Что выводит: Рамка, 'Something useful...for me', 'The name of our function is ...some_func', 'The doc_string of our function is ...some_func' (баг: выведено __name__ вместо __doc__), рамка, затем '<RETURN FROM some_func _><RETURN FROM some_func _>'.

Начало файла
"""
parameterized decorator (параметризированный декоратор)
или декоратор c параметрами
"""


from functools import wraps


def param_decorator(parameter):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            """Wrapper's doc_string"""
Показать файл целиком (36 строк)
"""
parameterized decorator (параметризированный декоратор)
или декоратор c параметрами
"""


from functools import wraps


def param_decorator(parameter):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            """Wrapper's doc_string"""
            print(50 * '*')
            result = func(*args, **kwargs)
            print(50 * '*')
            return parameter * f"<{result}>"
        return wrapper
    return decorator


@param_decorator(2)
def some_func(comment):
    """Doc_string for some_func"""
    print(f'Something useful...{comment}')
    print(f"The name of our function is ...{some_func.__name__}")
    print(f"The doc_string of our function is ...{some_func.__name__}")
    return 'RETURN FROM some_func _'


print(some_func('for me'))

# **************************************************
# Something useful...
# **************************************************
Проверьте себя: Сколько уровней вложенных функций требуется, чтобы декоратор мог принимать собственный параметр (как 2 в @param_decorator(2)), и зачем нужен именно такой порядок вложенности?
Открыть файл →
13
PythonИсполняемый пример16 строк

Базовая настройка модуля logging: уровни и обработчики

less_32__closure_decorator/theory_13_logging.py

Показывает минимальную настройку стандартного логирования: два обработчика (запись в файл log.log и вывод в консоль через StreamHandler), уровень INFO и формат с меткой времени, уровнем и сообщением. Демонстрирует пять уровней логирования по возрастанию серьёзности — debug, info, warning, error, critical — и то, что при level=INFO сообщение debug() отфильтровывается.

  • handlers = [FileHandler('log.log'), StreamHandler()]
  • logging.basicConfig(handlers=..., level=logging.INFO, format=...)
  • logging.debug/info/warning/error/critical — пять уровней
  • level=INFO — debug-сообщение не проходит фильтр

Что выводит: В консоль (и файл log.log) выводятся четыре строки — info, warning, error, critical с меткой времени; debug-сообщение отфильтровано, так как уровень logger'а установлен в INFO. Побочный эффект: скрипт создаёт файл log.log в текущей директории.

Файл целиком (16 строк)
import logging

handlers = [logging.FileHandler('log.log'),
            logging.StreamHandler()]
logging.basicConfig(handlers=handlers,
                    level=logging.INFO,
                    format='%(asctime)s - %(levelname)-8s - %(message)s')


x = 5

logging.debug(f"Logging debug for function {x}")
logging.info(f"Logging info for function {x}")
logging.warning(f"Logging warning for function {x}")
logging.error(f"Logging error for function {x}")
logging.critical(f"Logging critical for function {x}")
Проверьте себя: Почему сообщение logging.debug(...) не появляется в выводе, хотя оно написано в коде раньше остальных четырёх вызовов?
Открыть файл →
14
PythonИсполняемый пример29 строк

«Подумаем»: декоратор log_decorator, логирующий каждый вызов функции

less_32__closure_decorator/theory_14_lets_think!__logging_example.py

Практический пример декоратора для логирования: log_decorator оборачивает функцию, вызывает её, а затем пишет в лог строку с именем функции, переданными args/kwargs и результатом. На примере greeting(name) показано, что декоратор корректно логирует вызов, но сам возвращаемый результат не прокидывается наружу — wrapper не содержит return, поэтому декорированная greeting теряет своё исходное возвращаемое значение (в данном случае None, так как greeting и так ничего не возвращает).

  • log_decorator(func) → wrapper(*args, **kwargs): result = func(...)
  • logging.info(f'Вызов функции: {func.__name__} с аргументами ... и результатом {result}')
  • @log_decorator над greeting(name): print(f'Hello, {name}!')
  • your_name = 'Ilya'; greeting(your_name)

Что выводит: Строка лога 'INFO - Вызов функции: greeting с аргументами args: (\'Ilya\',) и kwargs: {} и результатом None', затем 'Hello, Ilya!' в консоли. Побочный эффект: пишет в файл log.log в текущей директории.

Начало файла
import logging
from functools import wraps


handlers = [logging.FileHandler('log.log'),
            logging.StreamHandler()]
logging.basicConfig(handlers=handlers,
                    level=logging.INFO,
                    format='%(asctime)s - %(levelname)-8s - %(message)s')


def log_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
Показать файл целиком (29 строк)
import logging
from functools import wraps


handlers = [logging.FileHandler('log.log'),
            logging.StreamHandler()]
logging.basicConfig(handlers=handlers,
                    level=logging.INFO,
                    format='%(asctime)s - %(levelname)-8s - %(message)s')


def log_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        logging.info(f'Вызов функции: {func.__name__} '
                     f'с аргументами args: {args} и kwargs: {kwargs} '
                     f'и результатом {result}')
    return wrapper


@log_decorator
def greeting(name):
    print(f"Hello, {name}!")


your_name = "Ilya"
greeting(your_name)


Проверьте себя: Почему в логе результат вызова greeting указан как None, хотя функция явно печатает приветствие?
Открыть файл →
15
PythonИсполняемый пример60 строк

Два декоратора подряд и __wrapped__ для их снятия

less_32__closure_decorator/theory_15_two_decorators.py

Показывает применение двух декораторов к одной функции (@decorator1 над @decorator2) и порядок их срабатывания — внешние звёздочки decorator1 оборачивают внутренние знаки равенства decorator2. Благодаря functools.wraps у каждой обёртки появляется атрибут __wrapped__, ссылающийся на предыдущий слой, поэтому some_func.__wrapped__.__wrapped__() позволяет вызвать исходную функцию, полностью минуя оба декоратора, а some_func.__wrapped__() — снять только внешний decorator1.

  • @decorator1 / @decorator2 — два декоратора на одной функции, оба используют @wraps
  • some_func() — оба декоратора отрабатывают: сначала decorator1, внутри decorator2
  • some_func.__wrapped__.__wrapped__() — вызов «голой» функции, минуя оба декоратора
  • f1() через @decorator1 над __wrapped__.__wrapped__() — накладываем только один декоратор заново
  • some_func.__wrapped__() — снятие только внешнего decorator1, decorator2 остаётся

Что выводит: Блок «Работают оба декоратора»: рамка из *, рамка из =, 'printing some_func _', рамка из =, рамка из *. Блок «Снять оба»: просто 'printing some_func _'. Блок «добавить первый»: рамка из *, 'printing some_func _', рамка из *. Блок «снять только внешний»: рамка из =, 'printing some_func _', рамка из =.

Начало файла
from functools import wraps


def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        f = func(*args, **kwargs)
        print(50 * '*')
        return f
    return wrapper

Показать файл целиком (60 строк)
from functools import wraps


def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '*')
        f = func(*args, **kwargs)
        print(50 * '*')
        return f
    return wrapper


def decorator2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper's doc_string"""
        print(50 * '=')
        f = func(*args, **kwargs)
        print(50 * '=')
        return f
    return wrapper


@decorator1
@decorator2
def some_func():
    print('printing some_func _')


"""
Как видим, изначально функцию some_func() декорируют оба декоратора.
Но есть метод, который позволяет отменить действие декораторов.
"""

print("------------------ Работают оба декоратора ------------------")
some_func()


print("\n\n------------------ Снять оба декоратора ------------------")
some_func.__wrapped__.__wrapped__()


print("\n\n------------------ Снять оба декоратора и добавить первый ------------------")
@decorator1
def f1():
    return some_func.__wrapped__.__wrapped__()


f1()


print("\n\n------------------ Снять только один внешний декоратор (@decorator1) ------------------")

def f2():
    return some_func.__wrapped__()


f2()
Проверьте себя: Почему some_func.__wrapped__ существует и указывает именно на функцию, обёрнутую decorator2 — какая часть кода это обеспечивает?
Открыть файл →
16
PythonИсполняемый пример7 строк

Заметка-заглушка про декораторы-классы

less_32__closure_decorator/theory_16_class_decorations.py

Файл не содержит исполняемого кода — это просто докстринг с двумя ссылками на видео (декоратор как функция, декоратор как класс), обозначающий тему следующего шага обучения, не раскрытую в остальных файлах урока.

  • Ссылка на видео про декоратор-функцию
  • Ссылка на видео про декоратор-класс
Файл целиком (7 строк)
"""
Декоратор - функция
https://youtu.be/394ZfiPJQ38?si=fZyPYeXhL8u2WQUg

Декоратор - класс
https://youtu.be/2szgmbn3cYM?si=LIXiExp2Uj8o3cZl
"""
Проверьте себя: Чем декоратор, реализованный как класс (через __init__ и __call__), отличается от декоратора-функции, разобранного в остальных файлах этого урока?
Открыть файл →