Вспомните: в главе 3 утилита расходов получила параметры запуска, окно с выбором папки, настройки в JSON и стала exe. Каждый раз программа была одним файлом, который рос сверху вниз. Для итогового проекта так не пойдёт: вы будете делать свою утилиту, и чтобы её можно было доделать, проверить и показать, её надо сначала придумать и разложить по полочкам. Этим и займёмся.
🎯 После урока сможете: выбрать задачу для итогового проекта по трём признакам, описать её в ТЗ на одну страницу (вход, выход, ошибки, интерфейс), разбить программу на функции «ввод — обработка — вывод» так, чтобы каждая делала одну работу, вынести эти функции в свой модуль utils.py и подключить его через import и from … import, защитить код запуска строкой if __name__ == "__main__": и проверить функции на маленьких данных через assert — ещё до окна.
Сквозной пример урока — «Разборщик чеков»: папка с выгрузками трат из банка в CSV превращается в итоги по категориям и отчёт JSON. Это образец, а не обязательная тема: проект вы делаете свой.
Какую задачу взять
Итоговый проект проваливается чаще всего не на коде, а на выборе задачи: «сделаю помощника для всего» или «что-нибудь с нейросетями». Хорошая задача проходит три проверки:
- Своя рутина. Вы сами делаете это руками и регулярно: раскладываете файлы, сводите таблицы, ищете в журналах. Тогда вы знаете, что считать правильным результатом, и программа пригодится после курса.
- Есть данные. Файлы для работы у вас уже есть или их легко получить: выгрузки, папка с фото, журнал. Нет данных — не на чем проверять, проект превращается в «окно с кнопками, которые ничего не делают».
- Результат проверяем. Можно взять маленький кусок данных, посчитать ответ вручную и сравнить с программой. «Подобрать лучший вариант» или «предсказать» так не проверишь.
Ещё одно ограничение — объём: на проект у вас около двух недель. Задача должна укладываться в инструменты курса: файлы, CSV и JSON, даты, регулярные выражения, командная строка, окно Tkinter и exe. Интернет, базы данных и нейросети — это другие курсы.
Запишите три-четыре своих рутины и прогоните каждую через три признака. Оставьте ту, где на все три ответ «да» и где ручная работа раздражает сильнее всего: доводить такую задачу до конца хочется самому.
Проверьте себя
Контрольный вопрос. Какая задумка проходит все три признака хорошего итогового проекта?
Подсказка: Пройдите по каждому варианту тремя вопросами: делаю ли я это руками сам, есть ли файлы, можно ли сверить ответ с подсчётом на бумаге?
Онлайн-проверка ответа появится позже
ТЗ на одну страницу
ТЗ (техническое задание) — короткий текст, где задача описана до кода. Одна страница, не больше: длинное ТЗ никто не дочитает, включая вас. В нём четыре обязательные части и строка о готовности:
- Вход — что программа получает: какие файлы, в какой кодировке, какие колонки, какие параметры.
- Выход — что и куда выдаёт: файл какого формата, текст на экране, в каком виде числа.
- Ошибки — что делать с плохими данными: нет файла, пустая папка, кривая строка. Здесь пригодится всё из главы 2.
- Интерфейс — как человек запускает программу: командная строка с параметрами или окно, какие в нём кнопки.
- Готово, когда — на каких данных и с каким результатом вы проверите, что программа работает.
ТЗ «Разборщика чеков» выглядит так:
Разборщик чеков — ТЗ
Зачем: раз в месяц трачу час, чтобы сложить траты из выгрузок
банка по категориям. Хочу за минуту.
Вход: папка с CSV-выгрузками (UTF-8, разделитель «;»,
колонки дата;категория;сумма, сумма может быть «1 240,50»).
Выход: файл otchet.json — категория -> сумма за все файлы,
плюс те же итоги на экране, по две цифры после точки.
Ошибки: папки нет или в ней нет CSV — сообщение, без падения;
строка с плохой суммой пропускается и считается;
«Кафе» и «кафе » — одна категория.
Интерфейс: окно — кнопка «Выбрать папку», кнопка «Посчитать»,
список итогов; выбранная папка запоминается в settings.json.
Готово, когда: на двух моих выгрузках за сентябрь итоги
совпадают с подсчётом вручную.

Заметьте: в ТЗ нет ни слова про функции и модули — только что программа делает для человека. Как она устроена внутри, решаем следующим шагом.
Проверьте себя
Контрольный вопрос. Коллега прислал ТЗ своей утилиты. Какой обязательной части в нём нет?
Переименователь фото — ТЗ
Вход: папка с фотографиями .jpg, имена вида IMG_4512.jpg.
Выход: те же файлы, переименованные в дату съёмки:
2026-09-14_001.jpg, 2026-09-14_002.jpg и так далее.
Интерфейс: командная строка, один параметр — путь к папке:
python foto.py D:\Фото\Сентябрь
Готово, когда: на папке из 20 фото все имена стали датами.
Подсказка: Сверьте заголовки ТЗ со списком обязательных частей из урока. Какой заголовок не встретился ни разу?
Онлайн-проверка ответа появится позже
Контрольный вопрос. В этом ТЗ все заголовки на месте, но по одной части программу всё равно не написать — непонятно, что делать. Чего не хватает?
Поиск в журнале — ТЗ
Вход: текстовый журнал программы (UTF-8), до 2 ГБ.
Выход: показывает результат.
Ошибки: файла нет — сообщение; строки в другой кодировке
пропускаются, их число выводится в конце.
Интерфейс: командная строка: путь к журналу и слово для поиска.
Подсказка: Прочитайте каждую часть и спросите себя: смогу ли я по этой строке написать код, не задав автору ни одного вопроса?
Онлайн-проверка ответа появится позже
Ввод — обработка — вывод
Почти любая утилита из курса делает три вещи: ввод — достаёт данные из файлов или параметров, обработка — считает, сортирует, ищет, вывод — пишет результат в файл или показывает человеку. Разложите программу на функции ровно по этим трём слоям. Тогда окно, командная строка и exe из главы 3 будут лишь по-разному вызывать одни и те же функции обработки.

У «Разборщика чеков» четыре функции:
import csv
import json
def parse_amount(text):
"""Строка суммы из выгрузки -> число: '1 200,50' -> 1200.5."""
return float(text.replace(" ", "").replace(",", "."))
def read_rows(path):
"""Ввод: строки одного CSV-файла выгрузки."""
with open(path, encoding="utf-8", newline="") as f:
return list(csv.DictReader(f, delimiter=";"))
def totals_by_category(rows):
"""Обработка: сумма трат по каждой категории."""
totals = {}
for row in rows:
category = row["категория"].strip().lower()
totals[category] = totals.get(category, 0) + parse_amount(row["сумма"])
return totals
def save_report(totals, path):
"""Вывод: отчёт в JSON."""
with open(path, "w", encoding="utf-8") as f:
json.dump(totals, f, ensure_ascii=False, indent=2)
read_rows(path)— ввод: открывает один CSV и возвращает список словарей (как в уроке 1.3);parse_amount(text)— обработка одной суммы: убирает пробелы-разделители и меняет запятую на точку;totals_by_category(rows)— обработка: складывает суммы по категориям, приводя имя категории к одному виду черезstrip().lower();save_report(totals, path)— вывод: записывает словарь итогов в JSON.
Одна функция — одна работа
Правило простое: функцию можно назвать одним глаголом без «и». read_rows читает, save_report сохраняет. Если в названии напрашивается «и» — «прочитай и посчитай», «посчитай и покажи» — функция делает две работы, и её стоит разрезать. Причины практические:
- функцию обработки можно проверить на списке из трёх строк, без файлов и окна;
- ту же обработку вызовут и окно, и командная строка — копировать код не придётся;
- сломалось — сразу видно, в каком слое искать.
Второй признак беды — две функции, которые делают одно и то же под разными именами. Так бывает, когда код копируют из старых программ. Оставьте одну и вызывайте её везде.
Проверьте себя
Контрольный вопрос. Какая функция делает две работы сразу и её стоит разрезать?
Исходный код для этого задания:
def read_lines(path): with open(path, encoding="utf-8") as f: return f.read().splitlines() def read_and_count(path): with open(path, encoding="utf-8") as f: lines = f.read().splitlines() return len([line for line in lines if "ошибка" in line]) def save_text(text, path): with open(path, "w", encoding="utf-8") as f: f.write(text)Подсказка: Попробуйте описать, что делает каждая функция, одним глаголом без «и».
Онлайн-проверка ответа появится позже
Контрольный вопрос. В модуле одна функция лишняя: её работу уже делает другая. Какую функцию удалить?
Исходный код для этого задания:
def parse_amount(text): return float(text.replace(",", ".")) def to_number(s): return float(s.replace(",", ".")) def total(amounts): return sum(parse_amount(a) for a in amounts) def report(value): return f"Итого: {value:.2f}"Подсказка: Найдите две функции с одинаковым телом. Из этой пары удалите ту, которую не вызывает ни одна функция модуля.
Онлайн-проверка ответа появится позже
Свой модуль: utils.py и import
Модуль — это обычный файл .py. Положите функции ввода, обработки и вывода в файл utils.py, а запуск программы — в main.py рядом, в той же папке. Python ищет модули сначала в папке запускаемого файла, поэтому main.py найдёт utils.py без настроек.
Подключить модуль можно двумя способами:
import utils
print(utils.parse_amount("1 200,50")) # имя модуля, точка, имя функции
from utils import parse_amount
print(parse_amount("1 200,50")) # функция доступна по своему имени
1200.5
1200.5
import utils— подключает модуль целиком, обращение через точку:utils.read_rows(...). Сразу видно, откуда функция;from utils import read_rows, save_report— берёт из модуля нужные имена, дальше они пишутся без приставки. Удобно, когда функций две-три;- при первом
importPython выполняет файл модуля целиком, сверху вниз: создаются его функции и переменные, и срабатывают всеprint, которые стоят вне функций. Повторныйimportтого же модуля файл заново не выполняет; - имя модуля в
importпишется без.py:import utils, неimport utils.py; - не называйте свой файл как модуль Python:
csv.pyилиjson.pyрядом с программой перекроют настоящие, иimport csvподключит ваш файл.
Вот main.py «Разборщика чеков». В нём нет ни одной строки обработки — только вызовы:
from pathlib import Path
from utils import read_rows, save_report, totals_by_category
def main():
rows = []
for path in sorted(Path("vygruzki").glob("*.csv")):
rows.extend(read_rows(path))
totals = totals_by_category(rows)
save_report(totals, "otchet.json")
for category, total in sorted(totals.items()):
print(f"{category}: {total:.2f}")
if __name__ == "__main__":
main()

Проверьте себя
Задание. Программа из двух файлов лежит в одной папке. Её запускают командой python main.py. Что она напечатает? Введите вывод целиком.
Исходный код для этого задания:
# ===== файл prices.py ===== def with_fee(amount): return amount + amount * 3 // 100 def rub(amount): return f"{amount} руб." # ===== файл main.py ===== import prices total = 0 for a in [500, 1200, 300]: total += prices.with_fee(a) print(prices.rub(total))Подсказка: Посчитайте with_fee для каждой суммы отдельно: что даёт целочисленное деление на 100? Потом сложите и посмотрите, как rub оформляет число.
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Задание. Программу запускают командой python main.py. Что выведет программа первой строкой? Введите строку целиком.
Исходный код для этого задания:
# ===== файл rules.py ===== print("загружаю правила") LIMIT = 1000 def is_big(amount): return amount > LIMIT # ===== файл main.py ===== from rules import is_big print("старт") print(sum(1 for a in [300, 1500, 999, 2400] if is_big(a)))Подсказка: Что делает Python, когда встречает строку с import или from … import: только запоминает имена или выполняет файл модуля сверху вниз?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Ошибки импорта
Две ошибки встречаются в каждом втором первом проекте.
Неверное имя. Python нашёл модуль, но в нём нет функции с таким именем — опечатка или функцию переименовали в одном файле и забыли во втором. Программа останавливается на строке from … import с ошибкой ImportError, а в тексте ошибки названо имя, которого не нашлось. Если опечатка в имени самого модуля, ошибка будет ModuleNotFoundError.
Круговой импорт. main.py импортирует utils.py, а utils.py в ответ импортирует что-то из main.py. Что происходит при python main.py: первая строка main.py отправляет Python выполнять utils.py. Первая строка utils.py просит main — и Python выполняет main.py ещё раз, уже как обычный модуль. Там первая же строка просит функцию из utils.py, а он выполнен только до своей первой строки: функции в нём ещё нет. Итог — ImportError. Каждый файл ждёт другой, и порядок строк внутри файлов это не лечит. Новые версии Python в тексте такой ошибки могут посоветовать переименовать файл — на случай, если он совпал по имени с чужим модулем. При круговом импорте дело не в имени: переименованный файл попадёт в тот же круг. Правило против этого: модуль ничего не берёт из main.py. Всё, что функции нужно, она получает параметрами, а настройки вроде лимитов живут в самом модуле или передаются из main.py при вызове.
# плохо: utils.py берёт LIMIT из main.py
from main import LIMIT
def is_big(amount):
return amount > LIMIT
# хорошо: лимит приходит параметром
def is_big(amount, limit):
return amount > limit
Проверьте себя
Контрольный вопрос. Программу запускают командой python main.py, и она сразу останавливается с ошибкой ImportError. В чём причина?
Исходный код для этого задания:
# ===== файл utils.py ===== def parse_amount(text): return float(text.replace(",", ".")) # ===== файл main.py ===== from utils import parse_amont print(parse_amont("12,5"))Подсказка: Сравните буква в букву имя после import в main.py и имя после def в модуле.
Онлайн-проверка ответа появится позже
Контрольный вопрос. Оба файла лежат в одной папке, имена написаны без опечаток, но python main.py падает с ImportError. Как исправить устройство программы?
Исходный код для этого задания:
# ===== файл utils.py ===== from main import LIMIT def is_big(amount): return amount > LIMIT # ===== файл main.py ===== from utils import is_big LIMIT = 1000 print(is_big(1500))Подсказка: Пройдите по стрелкам импорта: какой файл Python начинает выполнять, куда уходит и какое имя ещё не успело появиться? Изменится ли этот круг, если поменять имя файла?
Онлайн-проверка ответа появится позже
Запуск или импорт: if __name__ == "__main__"
Как сказано выше, при импорте Python выполняет файл модуля целиком. Значит, всё, что стоит в модуле вне функций, сработает и при импорте. А иногда модуль хочется и запустить отдельно — например, чтобы прогнать его проверки. Как файлу понять, запустили его или импортировали?
В каждом файле есть встроенная переменная __name__ (два подчёркивания с каждой стороны). Python сам кладёт в неё значение: у файла, который запустили командой python файл.py, это строка "__main__", у импортированного модуля — его имя без .py. Проверим на двух файлах:
# utils.py
print("в utils.py __name__ =", __name__)
# main.py
import utils
print("в main.py __name__ =", __name__)
Запуск python main.py:
в utils.py __name__ = utils
в main.py __name__ = __main__
Запуск python utils.py:
в utils.py __name__ = __main__
Отсюда стандартная строка в конце файла:
if __name__ == "__main__":
main()
- в
main.pyона запускает программу только при прямом запуске. Когда в главе 3 вы собирали exe, PyInstaller запускал именно этот файл — условие выполнится; - в
utils.pyпод этой строкой удобно держать проверки модуля: приpython utils.pyони выполнятся, а при импорте изmain.py— нет, и не будут мусорить в выводе программы.
Проверьте себя
Задание. Программу запускают командой python main.py. Что окажется в первой строке вывода? Введите строку целиком.
Исходный код для этого задания:
# ===== файл raschet.py ===== def share(total, people): return total / people print(__name__) # ===== файл main.py ===== import raschet print(raschet.share(900, 3))Подсказка: Какое значение Python кладёт в __name__ импортированному модулю? Оно связано с именем файла.
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Задание. Теперь тот же файл raschet.py запускают сам по себе: python raschet.py. Что он напечатает? Введите вывод.
Исходный код для этого задания:
# ===== файл raschet.py ===== def share(total, people): return total / people print(__name__)Подсказка: Кто здесь запускаемый файл, а кто импортированный модуль? Вспомните, какое значение получает __name__ у запускаемого файла.
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Задание. Программу запускают командой python main.py. Сколько строк она напечатает? Введите число.
Исходный код для этого задания:
# ===== файл vremya.py ===== def minutes(text): h, m = text.split(":") return int(h) * 60 + int(m) print("модуль время загружен") if __name__ == "__main__": assert minutes("1:30") == 90 assert minutes("0:05") == 5 print("проверка 1 пройдена") print("проверка 2 пройдена") # ===== файл main.py ===== from vremya import minutes for t in ["0:45", "1:10", "2:00"]: print(minutes(t))Подсказка: Разделите модуль на две части: что выполняется при любом импорте, а что спрятано под условием? Выполнится ли условие, если модуль импортировали?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Онлайн-проверка ответа появится позже
Проверка функций на маленьких данных: assert
Окно и exe — последний шаг. Сначала убедитесь, что функции считают верно, на данных, где ответ известен заранее. Для этого есть инструкция assert: после неё пишется условие, которое обязано быть истинным.
assert parse_amount("1 200,50") == 1200.5 # верно — ничего не происходит
assert parse_amount("99") == 100.0 # неверно — программа останавливается
Если условие истинно, assert молчит. Если ложно, программа останавливается с ошибкой AssertionError и показывает строку проверки, которая не прошла. Если функция внутри проверки сама упала — например float не смог разобрать строку, — вы увидите её ошибку, обычно ValueError. В обоих случаях сломанное место видно сразу.
Как подбирать проверки:
- 2–4 проверки на функцию обработки, данные — прямо в коде, 2–3 строки, ответ посчитан в уме;
- обычный случай (
"99"), случай из ТЗ ("1 200,50","кафе ") и пустой ([]— пустая папка); - проверки — в
utils.pyподif __name__ == "__main__":, запуск —python utils.py.
Вот проверки «Разборщика чеков» в конце utils.py:
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([]) == {}
print("utils.py: все проверки пройдены")
Запуск python utils.py:
utils.py: все проверки пройдены
А так выглядит запуск всей программы python main.py на двух выгрузках из урока — проверки модуля при этом молчат:
# vygruzki/2026-09-a.csv
дата;категория;сумма
2026-09-02;Продукты;1 240,50
2026-09-03;кафе;380
2026-09-05;Транспорт;65
# vygruzki/2026-09-b.csv
дата;категория;сумма
2026-09-09;продукты;2 015
2026-09-10;Кафе;420,30
кафе: 800.30
продукты: 3255.50
транспорт: 65.00
assert— инструмент разработчика, а не проверка ввода человека. Плохую строку в выгрузке или пустое поле в окне по-прежнему ловятtry/exceptи понятное сообщение, как в главе 2.
Положите
utils.py,main.pyи папкуvygruzkiс двумя CSV из урока в одну папку и запустите сначалаpython utils.py, потомpython main.py. Затем испортитеparse_amount— уберите.replace(" ", "")— и снова запуститеpython utils.py.
Проверьте себя
Контрольный вопрос. Файл проверок запустили. Какая проверка остановит программу первой?
Исходный код для этого задания:
def parse_amount(text): return float(text.replace(",", ".")) assert parse_amount("350") == 350.0 assert parse_amount("12,5") == 12.5 assert parse_amount("1 200") == 1200.0 assert parse_amount("0,99") == 0.99 print("всё верно")Подсказка: Пройдите проверки по порядку и для каждой подставьте строку в тело функции: что останется после replace и сможет ли float это разобрать?
Онлайн-проверка ответа появится позже
Задание. Соберите программу из двух файлов сами. main.py уже написан (он ниже, менять его не нужно). Напишите рядом модуль vremya.py с двумя функциями: minutes(text) переводит запись «часы:минуты» в число минут, fmt(total) превращает число минут в строку вида «2 ч 5 мин». В конце модуля, в блоке, который выполняется только при прямом запуске файла, поставьте по одной проверке assert на каждую функцию. Запустите python main.py. В поле ответа — число из последней строки вывода.
Исходный код для этого задания:
from vremya import minutes, fmt log = ["0:45", "1:20", "0:35", "2:05", "0:50", "1:15", "0:40", "1:30", "0:25", "2:10"] total = sum(minutes(t) for t in log) print(fmt(total)) print(total)Подсказка: Для minutes пригодится split(«:») и int, для fmt — целочисленное деление и остаток от деления на 60. Сначала добейтесь, чтобы python vremya.py прошёл проверки, потом запускайте main.py.
✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.
Онлайн-проверка ответа появится позже
Миссия: разложить «простыню»
Так выглядит программа, которую писали «сверху вниз» без функций. Она работает, но проверить её по частям, прикрутить к ней окно или командную строку трудно. Разложите её по правилам урока.
Задание. Проектная миссия «Из простыни — в два файла». Ниже программа одним куском: она складывает траты по категориям из выгрузки (данные вшиты в переменную DATA, 52 строки) и считает пропущенные строки. Разложите её на два файла в одной папке:
— utils.py: функция parse_line(line) возвращает пару (категория, сумма) или выбрасывает ValueError для плохой строки; функция add_to_totals(totals, category, value) добавляет сумму в словарь; новая функция top_category(totals) возвращает категорию с наибольшей суммой; в блоке только для прямого запуска — проверки assert для всех трёх функций;
— main.py: DATA, функция main() с циклом и выводом итогов, try/except вокруг parse_line, в конце строка «Больше всего: категория сумма», где сумма округлена round() до целого; вызов main() — только при прямом запуске файла.
Итоги по категориям и число пропущенных строк должны совпасть с выводом исходной программы. Приложите оба файла .py; в поле ответа — число из последней строки вывода вашего main.py.Принято, если:
1. число совпадает с эталоном;
2. приложены utils.py и main.py, которые это число выдают на данных из условия;Самопроверка перед сдачей (преподаватель смотрит на занятии):
3. в utils.py нет ни print, ни чтения DATA — только функции и проверки в блоке прямого запуска;
4. python utils.py проходит проверки, а python main.py их не печатает;
5. каждую функцию можно назвать одним глаголом без «и».Исходный код для этого задания:
DATA = """ 2026-09-01;продукты;720,59 2026-09-01;Связь;1 610,83 2026-09-02;аптека;3477 2026-09-02;Транспорт;426 2026-09-03;Транспорт;2 871,17 2026-09-03;Продукты;1 194,37 2026-09-04;Кафе ;552 2026-09-04;Продукты;628,32 2026-09-05;Продукты;962,87 2026-09-06;Транспорт;3 302,48 2026-09-06;кафе;1707 2026-09-07;продукты;391 2026-09-07;Подарки;511 2026-09-08;продукты;1185 2026-09-08;Подарки;608,15 2026-09-09;кафе;822 2026-09-09;Транспорт;523 2026-09-10;кафе 2026-09-11;аптека;1527,97 2026-09-11;Подарки;2 778 2026-09-12;аптека;2923 2026-09-12;продукты; 2026-09-13;Подарки;917 2026-09-13;аптека;779 2026-09-14;аптека;1011 2026-09-14;Подарки;975 2026-09-15;кафе;0 2026-09-16;аптека;2 079 2026-09-16;Связь;-150 2026-09-17;Транспорт;671,75 2026-09-17;Транспорт;3250,15 2026-09-18;Кафе ;1 675,49 2026-09-18;Связь;-150 2026-09-19;кафе;0 2026-09-19;продукты;3306 2026-09-20;кафе;2473 2026-09-21;Подарки;1051 2026-09-21;продукты;3 422,78 2026-09-22;аптека 2026-09-22;Транспорт;106 2026-09-23;продукты;2 723 2026-09-23;аптека;0 2026-09-24;Продукты;2192,59 2026-09-24;Продукты;2 442 2026-09-25;кафе;2 992 2026-09-26;Связь;3 128,66 2026-09-26;Связь;1632,79 2026-09-27;Кафе ;3122,75 2026-09-27;кафе;1633 2026-09-28;Транспорт;406 2026-09-28;Продукты;1 322 2026-09-29;Продукты;808 """ totals = {} bad = 0 for line in DATA.strip().splitlines(): parts = line.split(";") if len(parts) != 3: bad += 1 continue date, category, amount = parts category = category.strip().lower() try: value = float(amount.replace(" ", "").replace(",", ".")) except ValueError: bad += 1 continue if value <= 0: bad += 1 continue totals[category] = totals.get(category, 0) + value for category in sorted(totals): print(f"{category}: {totals[category]:.2f}") print("Пропущено строк:", bad)Подсказка: Начните с parse_line: перенесите в неё всё, что исходная программа делает с одной строкой, а каждое continue после bad += 1 замените на raise ValueError. Счётчик пропусков останется в main() в ветке except.
✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.
Онлайн-проверка ответа появится позже
Что дальше
Теперь вы умеете выбрать задачу для своего проекта, описать её в ТЗ на одну страницу, разложить программу на функции ввода, обработки и вывода по одной работе на функцию, вынести их в свой модуль и подключить через import или from … import, разделить запуск и импорт строкой if __name__ == "__main__": и проверить функции через assert до всякого окна. В следующем уроке соберёте свою утилиту целиком, проверите её перед тем, как отдавать людям, и подготовитесь к защите.
← Назад · ↑ В начало урока · ⌂ К навигатору курса · Вперёд →
