Сборка, проверка и защита: от модуля до exe, чек-лист готовности, README

Вспомните: в уроке 4.1 вы выбрали задачу, описали её на одной странице и разложили программу на функции: логика живёт в своём модуле utils.py, а главный файл её только импортирует и вызывает. В уроке 3.4 вы собирали exe и искали settings.json через app_dir(). Теперь соединяем всё: в каком порядке доводить проект до exe, как проверить его до того, как он уйдёт к людям, что написать в README и что показать на защите.

🎯 После урока сможете: довести свою утилиту до exe в порядке «логика → командная строка → окно → exe», прогнать её по чек-листу готовности (ошибки ввода, пустая папка, кодировки, настройки рядом с exe), написать README на полстраницы и подготовить пятиминутную защиту. В конце урока — итоговый проект курса.

Порядок сборки: логика → командная строка → окно → exe

Сквозной пример — «Разборщик чеков»: в папке лежат выгрузки покупок в CSV, программа считает итоги по категориям. Ваш проект будет своим, порядок тот же. Каждый следующий слой опирается на предыдущий, и ошибку ищут только в новом слое: всё, что под ним, уже проверено.

  1. Логика. Функции в utils.py работают на маленьких данных и проверены assert — это вы сделали в уроке 4.1. Окна ещё нет.
  2. Командная строка. Короткий cli.py принимает папку через argparse и печатает результат. Так программу проверяют на настоящих файлах за секунды, без щелчков мышью.
  3. Окно. okno.py на Tkinter вызывает те же функции из utils.py. Если окно показывает не то, а командная строка на тех же данных — то, ошибка в окне.
  4. Exe. Собираете окно через PyInstaller, как в уроке 3.4, и проверяете уже собранную программу.

Логика «Разборщика чеков» — тот самый utils.py из урока 4.1: parse_amount, read_rows, totals_by_category и save_report остаются как были, колонки те же — дата;категория;сумма. Командной строке и окну нужны ещё три короткие функции. Добавляем их в конец модуля, каждую со своей работой, и дописываем проверку в блок прямого запуска:

import csv
import json
import sys
from pathlib import Path

# ... parse_amount, read_rows, totals_by_category, save_report — как в уроке 4.1

def read_folder(folder):
    """Ввод: строки всех CSV-выгрузок из папки."""
    rows = []
    for path in sorted(Path(folder).glob("*.csv")):
        rows.extend(read_rows(path))
    return rows


def drop_bad_rows(rows):
    """Обработка: отсеивает строки, где сумма не читается числом."""
    good = []
    for row in rows:
        try:
            parse_amount(row["сумма"] or "")
        except ValueError:
            continue
        good.append(row)
    return good, len(rows) - len(good)


def app_dir():
    """Папка программы: рядом с exe после сборки, рядом с .py до неё."""
    if getattr(sys, "frozen", False):
        return Path(sys.executable).parent
    return Path(__file__).parent


if __name__ == "__main__":
    assert parse_amount("1 200,50") == 1200.5
    assert parse_amount("99") == 99.0
    rows = [{"категория": "Кафе", "сумма": "100"},
            {"категория": "кафе ", "сумма": "50,5"},
            {"категория": "такси", "сумма": "300"}]
    assert totals_by_category(rows) == {"кафе": 150.5, "такси": 300.0}
    assert totals_by_category([]) == {}
    bad = rows + [{"категория": "кафе", "сумма": "—"}, {"категория": "кафе", "сумма": None}]
    assert drop_bad_rows(bad) == (rows, 2)
    print("utils.py: все проверки пройдены")
  • read_folder(folder) — ввод: читает все CSV папки. Этот цикл жил в main.py урока 4.1; теперь он нужен и командной строке, и окну, поэтому переехал в модуль — копировать его не придётся;
  • drop_bad_rows(rows) — обработка: отсеивает строки, где сумма не читается числом, и возвращает хорошие строки и число пропущенных. Так выполняется пункт ТЗ «строка с плохой суммой пропускается и считается»: totals_by_category на такой строке упала бы с ValueError;
  • app_dir() — из урока 3.4: папка программы, чтобы otchet.json ложился рядом с exe.

Слой командной строки — cli.py. Он заменяет main.py из урока 4.1 и, как он, только вызывает функции модуля; запуск — под if __name__ == "__main__"::

import argparse

from utils import app_dir, drop_bad_rows, read_folder, save_report, totals_by_category


def main():
    parser = argparse.ArgumentParser(description="Итоги по чекам из папки")
    parser.add_argument("papka", help="папка с выгрузками CSV")
    args = parser.parse_args()

    rows, skipped = drop_bad_rows(read_folder(args.papka))
    if not rows:
        print("В папке нет чеков")
        return
    totals = totals_by_category(rows)
    save_report(totals, app_dir() / "otchet.json")
    for category, total in sorted(totals.items()):
        print(f"{category}: {total:.2f}")
    print("Пропущено строк:", skipped)


if __name__ == "__main__":
    main()

В папке vygruzki — две выгрузки из урока 4.1 и третья, 2026-09-c.csv, где в одной строке вместо суммы прочерк:

# vygruzki/2026-09-c.csv
дата;категория;сумма
2026-09-14;транспорт;120
2026-09-15;кафе;—

Запуск на этой папке и на пустой папке pusto:

> python cli.py vygruzki
кафе: 800.30
продукты: 3255.50
транспорт: 185.00
Пропущено строк: 1

> python cli.py pusto
В папке нет чеков

Итоги кафе и продуктов те же, что в уроке 4.1: третья выгрузка добавила 120 к транспорту, а строка с прочерком пропущена и посчитана. Рядом с cli.py появился otchet.json с теми же итогами.

Окно — okno.py — вызывает те же функции из обработчика кнопки. Сравните с cli.py: в окне нет ни одной строки про CSV и суммы, только выбор папки и показ результата:

import tkinter as tk
from tkinter import filedialog, messagebox

from utils import app_dir, drop_bad_rows, read_folder, save_report, totals_by_category


def vybrat_papku():
    papka = filedialog.askdirectory()
    if not papka:
        return
    rows, skipped = drop_bad_rows(read_folder(papka))
    if not rows:
        messagebox.showwarning("Чеки", "В папке нет чеков")
        return
    totals = totals_by_category(rows)
    save_report(totals, app_dir() / "otchet.json")
    stroki = [f"{kat}: {summa:.2f}" for kat, summa in sorted(totals.items())]
    stroki.append(f"Пропущено строк: {skipped}")
    messagebox.showinfo("Итоги", "\n".join(stroki))


def main():
    root = tk.Tk()
    root.title("Разборщик чеков")
    tk.Button(root, text="Выбрать папку", command=vybrat_papku).pack(padx=40, pady=20)
    root.mainloop()


if __name__ == "__main__":
    main()

Окно здесь урезано до одной кнопки, чтобы было видно главное. В ТЗ из урока 4.1 у окна ещё список итогов и запоминание папки в settings.json — это делается так же, как в уроке 3.3.

Собирают главный файл окна. Модуль utils.py отдельно указывать не нужно: PyInstaller сам находит модули, которые импортирует скрипт, — как он нашёл json и pathlib в уроке 3.4.

python -m PyInstaller --onefile --windowed --name Cheki okno.py
Дорожная карта сборки из четырёх шагов слева направо. 1 «Логика: utils.py, проверка assert». 2 «Командная строка: cli.py папка → итоги в терминале». 3 «Окно: okno.py, кнопка вызывает ту же функцию». 4 «Exe: PyInstaller --onefile --windowed». Под шагами 2–4 стрелки вниз к общему блоку «utils.py»: все слои вызывают одну логику.

Проверьте себя

Контрольный вопрос. Коллега сделал утилиту расходов в обход порядка: написал функции и сразу собрал exe из окна, без командной строки. Exe показывает неверный итог. Почему ошибку теперь трудно найти?

АPyInstaller искажает расчёты, достаточно пересобрать exe
Бпод подозрением сразу три слоя, ни один из которых не проверен на настоящих файлах: логика, окно и сборка
Вбез командной строки Python не может прочитать CSV
Гокно Tkinter нельзя собирать в exe

Подсказка: Какие слои успели доказать, что работают на настоящих файлах, до того как появился exe?

Онлайн-проверка ответа появится позже

Контрольный вопрос. В окне «Разборщика чеков» итог по категории «кафе» — 0, а та же папка через cli.py даёт верный итог. Где искать ошибку?

Ав функции totals_by_category в utils.py
Бв CSV-файлах папки
Вв обработчике кнопки в okno.py
Гв PyInstaller — пересобрать exe

Подсказка: Какие слои уже доказали, что работают на этих данных? Что осталось непроверенным?

Онлайн-проверка ответа появится позже

Задание. Программа ниже разбирает строки «категория;сумма». Что она напечатает? Введите строку вывода целиком.

Исходный код для этого задания:

strok = ["продукты;450", "транспорт;3,2", "кафе;1200", "продукты;"]
itogo = 0
oshibki = 0
for s in strok:
    kat, summa = s.split(";")
    try:
        itogo += float(summa)
    except ValueError:
        oshibki += 1
print(itogo, oshibki)

Подсказка: Пройдите строки по одной: какие значения float() примет, а на каких выбросит ValueError? Помните, что float понимает только точку.

✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.

Онлайн-проверка ответа появится позже

Чек-лист готовности: что проверить до отправки

Программа, которая работает на ваших файлах, ещё не готова: у коллеги будут другие файлы, другая папка и другие ошибки. Перед сборкой и после неё пройдите пять пунктов. Каждый ловит свой сбой.

  1. Ошибки ввода. Сумма с запятой, прочерк, пустая ячейка, буквы в поле окна. Программа не падает: try/except ValueError (урок 2.1), строка пропускается и учитывается, в окне — messagebox.showerror, а не трассировка.
  2. Пустые данные. Пустая папка, папка без CSV, файл с одной шапкой. Программа говорит «нет чеков», а не делит на ноль и не печатает пустой отчёт. Проверьте if not … до расчётов среднего и процентов.
  3. Кодировки. Выгрузка из Excel приходит в utf-8-sig (с меткой BOM) или в cp1251. Откройте файл каждой кодировки, которая встречается у вас, и поймайте UnicodeDecodeError, если файл не читается.
  4. Настройки и отчёт рядом с exe. Пути к settings.json и к файлу отчёта — через app_dir() из урока 3.4, не относительные. Запустите собранный exe из другой папки.
  5. Чистая машина. Запустите exe у коллеги или на компьютере без Python. Только там видно, что ничего не забыто.

Пустые данные проверяют маленькой функцией. Без проверки if not summy пустой список дал бы ZeroDivisionError:

def sredniy(summy):
    if not summy:
        return 0
    return round(sum(summy) / len(summy), 1)


assert sredniy([]) == 0
assert sredniy([100, 200]) == 150.0
print("проверки пройдены")
проверки пройдены
Таблица из пяти строк: слева сбой красным, справа средство зелёным. ValueError на прочерке или пустой сумме → try/except и счётчик пропусков. ZeroDivisionError на папке без CSV → проверка if not до расчёта. UnicodeDecodeError на выгрузке из Excel → encoding utf-8-sig или cp1251. settings.json не найден при запуске из другой папки → app_dir(). У коллеги нет модуля или файла → запуск на компьютере без Python.

Проверьте себя

Контрольный вопрос. Коллега открыл exe двойным щелчком — настройки подхватились. Запустил тот же exe из терминала, находясь в своей папке Документы, — программа работает со значениями по умолчанию. Какой пункт чек-листа ловит этот сбой?

АОшибки ввода
БПустые данные
ВКодировки
ГНастройки и отчёт рядом с exe

Подсказка: От чего зависит относительный путь и что поменялось между двумя запусками?

Онлайн-проверка ответа появится позже

Контрольный вопрос. На выгрузке из банка программа падает с UnicodeDecodeError, на ваших тестовых файлах — нет. Какой пункт чек-листа ловит этот сбой?

АОшибки ввода
БПустые данные
ВКодировки
ГЧистая машина

Подсказка: Что означает слово Decode в названии ошибки: программа не поняла число или не смогла прочитать байты файла как текст?

Онлайн-проверка ответа появится позже

Контрольный вопрос. Пользователь выбрал в окне папку, где нет ни одного CSV, и программа упала с ZeroDivisionError при расчёте среднего чека. Какой пункт чек-листа ловит этот сбой?

АОшибки ввода
БПустые данные
ВКодировки
ГНастройки и отчёт рядом с exe

Подсказка: Сколько сумм попало в расчёт и что тогда стоит в знаменателе?

Онлайн-проверка ответа появится позже

Задание. «Разборщик чеков» собран с —onefile в файл D:\Утилиты\cheki\cheki.exe. Его запустили из терминала, находясь в папке C:\Users\ученик. Чтение настроек сделано через app_dir() из урока 3.4. В какой папке программа будет искать settings.json? Введите полный путь к папке, без имени файла и без слеша в конце.

Исходный код для этого задания:

def app_dir():
    if getattr(sys, "frozen", False):
        return Path(sys.executable).parent
    return Path(__file__).parent


settings_path = app_dir() / "settings.json"

Подсказка: Программа собрана, значит, какая ветка app_dir() сработает? На что указывает sys.executable в собранной программе?

✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.

Онлайн-проверка ответа появится позже

Задание. Напишите функцию dolya(summa, vsego), которая возвращает долю категории в процентах, округлённую round() до одного знака, а если vsego равно 0 — возвращает 0 и не падает. Вызовите print(dolya(0, 0), dolya(380, 2030)). Введите строку, которую напечатает программа.

Исходный код для этого задания:

def dolya(summa, vsego):
    ...  # ваш код


print(dolya(0, 0), dolya(380, 2030))

Подсказка: Сначала проверка пустого случая и return, потом расчёт: сумма делится на итог и умножается на 100.

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Онлайн-проверка ответа появится позже

README на полстраницы

README — текстовый файл README.md или README.txt рядом с программой. Его читает человек, который видит вашу утилиту впервые: коллега, преподаватель, вы через полгода. Полстраницы хватает, если ответить на пять вопросов:

  • Что делает — одно предложение.
  • Что нужно на входе — какие файлы, в каком формате, пример строки.
  • Как запустить — exe двойным щелчком или команда с параметрами.
  • Что получится — куда ляжет отчёт и как он выглядит.
  • Настройки и ограничения — где settings.json, что программа не умеет, под какую систему собрана.
# Разборщик чеков

Считает итоги покупок по категориям по всем CSV в выбранной папке.

## Что нужно
CSV в UTF-8 с разделителем «;» и шапкой дата;категория;сумма,
например: 2026-09-02;Продукты;1 240,50

## Как запустить
Cheki.exe — двойной щелчок, кнопка «Выбрать папку».
Из терминала без окна: python cli.py путь_к_папке

## Что получится
Итоги по категориям на экране и файл otchet.json рядом с Cheki.exe.
«Кафе» и «кафе » — одна категория. Строки, где сумма не число,
пропускаются, их число показывается.

## Настройки и ограничения
settings.json рядом с Cheki.exe. Собрано под Windows 10,
на macOS не запустится. Антивирус может спросить разрешение.

Проверьте себя

Контрольный вопрос. Ниже README чужого проекта. Чего в нём не хватает, чтобы коллега смог воспользоваться программой?

# Сортировщик фото

Раскладывает фотографии из папки по подпапкам год/месяц

по дате съёмки.

## Что получится

В выбранной папке появятся подпапки вида 2026/09.

## Ограничения

Собрано под Windows. Видео не трогает.

Аописания, что делает программа
Бтого, как её запустить
Втого, что получится на выходе
Гограничений программы

Подсказка: Пройдите по пяти вопросам README из урока и отметьте, на какие этот текст отвечает.

Онлайн-проверка ответа появится позже

Защита проекта: пять минут

Защита проходит на занятии: вы показываете проект преподавателю и группе. Если вы проходите курс без группы, запишите экран на 3–5 минут по тому же плану и приложите запись к сдаче. Не пересказывайте код построчно — его смотрят по вложению. Пять минут делятся так:

  1. Задача (1 минута). Какую свою рутину программа убирает и сколько времени она занимала руками.
  2. Демонстрация (2–3 минуты). Запуск exe или командной строки на примере входа — копии ваших данных, где настоящие значения заменены выдуманными: вход → результат, который совпадает со строкой «Готово, когда» из ТЗ. Затем один плохой случай из чек-листа — пустая папка или кривая строка — и как программа на него отвечает.
  3. Одна трудность (1 минута). Что не получалось, как нашли причину, как решили. Это самая ценная минута: по ней видно, что проект ваш.

После — вопросы. Типичные: почему эта функция живёт в utils.py, что будет, если подать другой файл, где программа ищет настройки после сборки. Если exe не собрался — расскажите, на каком шаге и с какой ошибкой: честный разбор в README даёт половину баллов за сборку.

На защите и во вложении — только код, README, ТЗ, пример входа и скриншоты. Свои настоящие выгрузки, чеки и личные файлы не прикладывайте и не показывайте на общем экране: для демонстрации сделайте копию с выдуманными значениями — это и будет пример входа.

Полоса времени на пять минут, разделённая на три отрезка: «Задача — 1 мин», «Демонстрация: вход → результат + один плохой случай — 2–3 мин», «Одна трудность и как решили — 1 мин». Справа отдельный блок «Вопросы».

Проверьте себя

Контрольный вопрос. Какой план пятиминутной защиты соответствует уроку?

А4 минуты читать код построчно, 1 минута на вопросы
Бзадача, демонстрация на примере входа с одним плохим случаем, одна трудность и как решили
Впоказать только README, программу не запускать
Грассказать историю языка Python и перечислить все использованные модули

Подсказка: Из каких трёх частей состоят пять минут и что в них показывают вместо кода?

Онлайн-проверка ответа появится позже

Итоговый проект: своя утилита

Итоговая работа курса — ваша утилита для вашей рутины, от задумки урока 4.1 до exe. «Разборщик чеков» — образец, а не обязательная тема: подойдёт сортировщик файлов, сводка по журналу, переименование фото, отчёт по таблице — любая задача, где есть данные и проверяемый результат. Проект проверяет преподаватель по критериям из условия задания, балл — их сумма, максимум 15, зачёт — от 9.

Задание. Итоговый проект «Своя утилита». Сделайте программу для своей рутины по порядку урока: ТЗ на одну страницу → логика в своём модуле с проверками assert → интерфейс → exe. Интерфейс — одно из двух, по вашей задаче: окно Tkinter или командная строка с параметрами argparse. Пройдите чек-лист готовности и напишите README.

Что сдать: один архив .zip с проектом:
— ТЗ на одну страницу (TZ.md или TZ.txt) из пяти частей урока 4.1. В строке «Готово, когда» укажите пример входа из архива и результат, посчитанный вручную;
— все файлы .py: модуль с логикой и главный файл;
— README;
— пример входного файла с выдуманными данными (3–10 строк);
— скриншот окна или терминала с результатом;
— если exe собран — скриншот папки dist с exe и скриншот запущенного exe; если не собран — разбор в README: на каком шаге, с какой ошибкой и почему.
Сам exe и папки build/dist в архив не кладите. Свои настоящие файлы и личные данные не прикладывайте.

Защита: на занятии — 5 минут: какую рутину убирает программа; показ на примере входа (копия ваших данных с выдуманными значениями) и одного плохого случая из чек-листа; одна трудность и её решение; затем вопросы преподавателя. Если проходите курс без группы — запись экрана на 3–5 минут по тому же плану: файл в архиве или ссылка в ответе.

Как оценят (15 баллов):
1. ТЗ на одну страницу: есть все пять частей — вход, выход, ошибки, интерфейс, «Готово, когда»; 1 — все пять на месте, 0 — хоть одной нет — 1 балл.
2. Верный результат: на примере входа из «Готово, когда» программа выдаёт результат, записанный в ТЗ; 2 — совпадает полностью, 1 — совпадает частично (часть файлов или итогов), 0 — не совпадает или не запустилась — 2 балла.
3. Плохие данные не роняют программу (проверяется на защите): 2 — и ошибка ввода (кривая строка, буквы в поле), и пустой вход (пустая папка или пустой файл) дают понятное сообщение вместо трассировки; 1 — только один из двух случаев; 0 — оба падают — 2 балла.
4. Логика в своём модуле: в главном файле (cli.py, окно или main.py) нет расчётов, только вызовы функций модуля — 1 балл.
5. Одна функция — одна работа: каждую функцию модуля можно назвать одним глаголом без «и» — 1 балл.
6. Проверки assert в модуле под if __name__ == «__main__»: запуск модуля напрямую проходит их без ошибок — 1 балл.
7. Интерфейс по задаче — одно из двух: окно Tkinter или командная строка с параметрами argparse — 1 балл.
8. Настройки или отчёт сохраняются в JSON или CSV, путь — через app_dir() — 1 балл.
9. Exe: 2 — собран и запускается (скриншот папки dist с exe и скриншот запущенной программы); 1 — не собран, но в README разобраны шаг, текст ошибки и причина (macOS, антивирус и т. п.); 0 — ни того ни другого — 2 балла.
10. README отвечает на пять вопросов: что делает, вход, запуск, результат, настройки и ограничения — 1 балл.
11. Защита по плану из трёх частей: какую рутину убирает программа, показ на примере входа вместе с плохим случаем, рассказ об одной трудности и её решении — 1 балл.
12. Ответы на вопросы: объясняет, зачем в его коде любая функция, на которую укажет преподаватель — 1 балл.

Зачтено — от 9 баллов из 15, при этом за «Верный результат» не 0.

Онлайн-проверка ответа появится позже

Что дальше

Теперь вы умеете доводить свою программу до exe по слоям — логика, командная строка, окно, сборка, — проверять её по чек-листу готовности, писать README, по которому ею может пользоваться другой человек, и защищать проект за пять минут. Курс «Python для своих задач» пройден: у вас есть утилита, которая экономит ваше время и запускается у людей без Python. Вернитесь к навигатору курса, если захотите повторить тему, к которой привязан ваш проект.

← Назад · ↑ В начало урока · ⌂ К навигатору курса · Вперёд →

Школа Виктора Комлева