UpPetto

This is a real course generated by UpPetto — unedited.

AI-generated. Two models wrote it, a third checked every risky claim against sources, a fourth re-checked.

Create my own course

Итоговая практика: рефакторинг C-style Python в идиоматичный код

core самостоятельно провести код-ревью и рефакторинг чужого 'C-style' Python-кода, устранив все паттерны, изученные в курсе

Это занятие не вводит новых понятий — оно проверяет, стали ли изученные идиомы вашим первым инстинктом, а не «дополнительной опцией», о которой вспоминаешь после ревью. Ниже — типичный фрагмент, который пишет опытный C-программист в первую неделю на Python: он рабочий, он проходит тесты, и в нём одновременно нарушено почти всё, что вы прошли в предыдущих модулях.

def process_records(records, config=None, errors=[]):
    if config is None:
        config = {}
    result = []
    for i in range(len(records)):
        rec = records[i]
        f = open(config.get("log_path", "log.txt"), "a")
        try:
            if rec["value"] < 0:
                errors.append(rec)
                status = -1
            else:
                result.append(rec["value"] * 2)
                status = 0
            f.write(str(status) + "\n")
        finally:
            pass
        f.close()
    return result, errors

Разбор по пунктам, каждый — отсылка к пройденному модулю:

  1. errors=[] в сигнатуре — классическая ловушка изменяемого значения по умолчанию. Список создаётся один раз при определении функции и переживает между вызовами: второй вызов process_records увидит «чужие» ошибки от первого. Это разобрано в модуле про функции и изменяемые значения по умолчанию — но здесь ловушка встречается в связке с остальными антипаттернами, что типично для реального код-ревью.
  2. for i in range(len(records)) + records[i] — индексный цикл там, где нужна прямая итерация for rec in records. Не только менее читаемо, но и провоцирует ошибки границ, от которых итерация по элементам защищает.
  3. status = -1 / status = 0, запись кода в файл — перенос C-идиомы «код возврата вместо исключения» в контекст, где для сигнализации проблемы уместнее исключение (raise ValueError(...) или собственный класс ошибки) либо, если это не ошибка, а бизнес-случай, — просто ветвление без имитации errno.
  4. open(...) без with, f.close() в конце тела цикла, finally: pass — ручное управление ресурсом без гарантии закрытия при исключении внутри try. Плюс файл открывается заново на каждой итерации цикла — проблема и производительности, и корректности одновременно.
  5. Возврат пары (result, errors) вместо исключения/явной структуры — работает, но смешивает «результат» и «накопленные проблемы» в одном плоском тюпле, что усложняет вызывающий код.
flowchart LR
    A1["for i in range(len(x)): x[i]"] --> B1["for item in x / enumerate(x)"]
    A2["status = -1 / 0, запись кода ошибки"] --> B2["raise SpecificError(...)"]
    A3["errors=[] в сигнатуре функции"] --> B3["errors: list | None = None,<br/>создание внутри тела"]
    A4["open(...) + вручную close()"] --> B4["with open(...) as f: ..."]
Соответствие C-стиля антипаттернов питоническим идиомам

Рефакторинг с учётом всего вышеперечисленного:

class NegativeValueError(Exception):
    def __init__(self, record):
        super().__init__(f"negative value in record: {record}")
        self.record = record

def process_records(records, config=None):
    config = config or {}
    log_path = config.get("log_path", "log.txt")
    result = []
    errors = []
    with open(log_path, "a") as f:
        for rec in records:
            if rec["value"] < 0:
                errors.append(rec)
                f.write("error\n")
                continue
            result.append(rec["value"] * 2)
            f.write("ok\n")
    return result, errors

Заметьте: файл открывается один раз на весь вызов (а не на итерацию), with гарантирует закрытие даже при исключении внутри цикла, errors создаётся внутри функции, итерация идёт по значениям без индексов. Класс NegativeValueError определён на случай, если по требованиям это должна быть настоящая исключительная ситуация, а не штатная бизнес-ветка — выбор между raise и обычным if зависит от того, ошибка ли это семантически, а не от привычки всегда сигнализировать через код возврата.

Разобранный пример

Ещё один частый паттерн — «накопление флага успеха вручную»:

# C-стиль
success = True
for item in items:
    if not validate(item):
        success = False
        break
if not success:
    print("validation failed")
    return None

Питонический эквивалент использует all()/any() с генераторным выражением, без промежуточного флага:

if not all(validate(item) for item in items):
    raise ValueError("validation failed")

Логика та же, но нет мутируемой переменной-флага, дублирующей то, что уже выражено самим условием.

Попробуй сейчас

Возьмите следующий фрагмент и самостоятельно проведите ревью и рефакторинг, отметив для себя каждый исправленный антипаттерн (их минимум четыре):

def load_users(paths, cache={}):
    users = []
    for i in range(len(paths)):
        path = paths[i]
        if path in cache:
            users.append(cache[path])
            continue
        f = open(path)
        data = f.read()
        f.close()
        if len(data) == 0:
            users.append(None)
        else:
            u = parse_user(data)
            cache[path] = u
            users.append(u)
    return users

Ожидаемый результат: cache={} заменён на явное отсутствие изменяемого значения по умолчанию (либо кэш создаётся снаружи и передаётся осознанно, если сохранение между вызовами — реальное требование), open через with, индексный цикл заменён на for path in paths, при пустом файле или ошибке парсинга рассмотрен вариант с исключением вместо None как «тихого» кода ошибки — по ситуации.

Получилось, если…

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

Вывод

Идиоматичный Python — это не «синтаксис получше», а набор привычек, которые снимают с вас ответственность, обязательную в C: ручное управление ресурсом, ручную проверку кода ошибки, ручной обход по индексу, ручной список «что не забыть очистить». Код-ревью C-style Python — это чек-лист именно по этим пунктам.

AI-generated · source-grounded review

🛡 Fact-checked: 1 risky claim verified · 2 removed · confidence: high · figures: 1
[unconfirmed by second model] errors=[] как значение по умолчанию создаётся один раз и переиспользуется между вызовами
KB содержит тему «Функции, области видимости и изменяемые значения по умолчанию» и ссылочную семантику изменяемых объектов
Second model: База знаний упоминает изменяемые значения по умолчанию в ссылках разделов, но не раскрывает конкретную ловушку с errors=[] в сигнатуре функции.
[verified] Итерация for item in items предпочтительнее for i in range(len(...))
прямо утверждается в KB [5, 27]
[unconfirmed by second model] with гарантирует закрытие файла, ручной close() — нет
KB указывает на контекстные менеджеры/with как замену ручному управлению ресурсами
Second model: База знаний упоминает контекстные менеджеры with, но не обсуждает конкретно гарантии закрытия файлов по сравнению с ручным close().
[unconfirmed by second model] Figure (mermaid): соответствие C-антипаттернов питоническим идиомам
все четыре соответствия подтверждены KB (итерация, исключения вместо кодов, изменяемые значения по умолчанию, with); исправлена опечатка метки «-1 / -0»
Second model: База знаний не содержит описания диаграммы соответствия C-антипаттернов питоническим идиомам.
[removed] Формулировка «ошибки границ, от которых Python в остальном защищает» уточнена: KB говорит, что итерация по элементам менее подвержена ошибкам индексации, а не что язык защищает в целом
[removed] В mermaid-схеме исправлена опечатка метки «status = -1 / -0» на «status = -1 / 0, запись кода ошибки» для соответствия тексту урока

A second model re-checked this lesson's claims and corroborated 1 of 4 , and could not confirm 3 either way. This is incomplete corroboration, not a disagreement.

Key concepts: рефакторинг код-ревью идиоматичность
Tell me more 🔒 Didn't understand — explain simply 🔒 Show examples 🔒 Sources 🔒

On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course

Check yourself

1. Почему errors=[] в сигнатуре функции — ошибка, а не просто стиль?
2. Что не так с открытием файла внутри for-цикла по каждой записи вместо одного open вне цикла с with?
3. Какой признак сигнализирует, что фрагмент Python написан 'в стиле C', а не идиоматично?
On your own course, these are marked as you answer