🏠 Домашнее задание

⚡ Суть задания

Напишите программу sales_report.py, которая читает файл продаж и создаёт структуру отчётов по годам, месяцам и категориям.

Результат: папка с отчётами, включающая monthly_report.txt и файлы категорий для каждого периода.

📋 Формулировка задания

Исходное задание:

Напишите программу, которая обрабатывает текстовый файл с данными о продажах. Используйте файл sales_data.txt.

  1. Принимайте аргументы командной строки: python sales_report.py <input_file> <output_directory>.
  2. Читайте данные из файла, где каждая строка имеет формат имя,дата,сумма,категория,город.
  3. Сгруппируйте данные по годам и месяцам, создавая для каждого периода отдельную папку.
  4. Создайте monthly_report.txt с суммарной выручкой по каждой категории и общей суммой.
  5. Создайте файлы для каждой категории с записями дата,имя продавца,сумма, отсортированными по дате.

🔧 Шаг 0. Подготовка окружения

Перед началом работы настройте рабочее окружение. Эти шаги одинаковы для всех уроков.

📁 Структура репозитория: мы используем один репозиторий для всех практических работ курса. Это удобнее, чем много отдельных репозиториев: общий .gitignore, единая история, легко переключаться между уроками.
1

Создайте (или откройте) рабочую папку

В VS Code нажмите Ctrl+K Ctrl+O → выберите или создайте папку python-fundamentals-practice. Это будет корень вашего учебного репозитория.

# Или в терминале VS Code (Ctrl + `):
mkdir python-fundamentals-practice
cd python-fundamentals-practice
2

Инициализируйте Git-репозиторий (один раз)

Если папка ещё не является git-репозиторием:

git init

# Создайте базовые файлы
echo "# Python Fundamentals — Практические работы" > README.md
echo "venv/" > .gitignore
echo "__pycache__/" >> .gitignore
echo "*.pyc" >> .gitignore
echo ".env" >> .gitignore
echo "reports/" >> .gitignore

git add README.md .gitignore
git commit -m "init: базовая структура репозитория"
3

Создайте папку для текущего урока

# Структура: lesson-XX/
mkdir -p lesson-53
cd lesson-53
4

Создайте виртуальное окружение (venv)

# Windows (PowerShell / CMD)
python -m venv venv
venv\Scripts\activate

# Mac / Linux
python3 -m venv venv
source venv/bin/activate

После активации в начале строки терминала появится (venv). Все дальнейшие команды выполняйте внутри venv.

5

Создайте ветку для задания

# Вернитесь в корень репозитория
cd ../..

# Создайте и переключитесь на ветку
git checkout -b lesson/53-practice-13

# Отправьте ветку на удалённый репозиторий (если настроен)
git push -u origin lesson/53-practice-13

Именование веток: lesson/XX-краткое-название. Это позволяет легко находить нужный урок.

🛠️ Шаги выполнения задания

Теперь, когда окружение готово, приступайте к решению.

1

Шаг 1: создайте входной файл

Создайте файл sales_data.txt с тестовыми данными:

Olivia Suarez,2024-08-02,4565,Electronics,Dallas
Jennifer Jacobs,2023-08-19,4963,Automotive,London
Erin Johnson,2024-08-29,1796,Clothing,Miami
Cynthia Maddox,2025-02-01,538,Electronics,Chicago
Kendra Martinez,2025-02-01,3799,Electronics,New York
Rachel Miller,2025-02-02,1097,Electronics,Boston
Michael Brown,2024-12-15,8200,Automotive,Seattle
Sarah Davis,2025-01-10,1500,Clothing,Los Angeles
John Wilson,2025-01-12,2300,Electronics,Houston
Emma Taylor,2025-02-05,6500,Groceries,Phoenix
2

Шаг 2: реализуйте чтение и парсинг

Создайте функцию read_sales_data(file_path), которая открывает файл в кодировке utf-8, разбивает строки по , и возвращает список словарей.

def read_sales_data(file_path):
    sales = []
    with open(file_path, "r", encoding="utf-8") as file:
        for line in file:
            parts = line.strip().split(",")
            if len(parts) != 5:
                continue
            name, date, amount, category, city = parts
            year, month, day = date.split("-")
            sales.append({
                "name": name,
                "date": date,
                "amount": int(amount),
                "category": category,
                "year": year,
                "month": month,
            })
    return sales
3

Шаг 3: сгруппируйте данные

Используйте defaultdict для группировки записей и подсчёта сумм:

from collections import defaultdict

grouped_sales = defaultdict(list)
category_totals = defaultdict(lambda: defaultdict(int))

for record in sales:
    key = (record["year"], record["month"], record["category"])
    grouped_sales[key].append(record)
    category_totals[(record["year"], record["month"])][record["category"]] += record["amount"]
4

Шаг 4: создайте файлы отчётов

Напишите функции write_category_reports и write_monthly_reports. Используйте os.path.join для путей и os.makedirs(..., exist_ok=True) для создания папок.

5

Шаг 5: добавьте обработку аргументов

Проверьте sys.argv в начале программы:

import sys

if len(sys.argv) < 3:
    print("Usage: python sales_report.py <input_file> <output_directory>")
    sys.exit(1)

input_file = sys.argv[1]
output_directory = sys.argv[2]

✅ Полное решение

Ниже приведён эталонный вариант. Совет: попробуйте выполнить задание самостоятельно, прежде чем смотреть ответ.

# sales_report.py
import os
import sys
from collections import defaultdict


def read_sales_data(file_path):
    """Считывает данные из .txt файла и возвращает список записей."""
    sales = []

    with open(file_path, "r", encoding="utf-8") as file:
        for line in file:
            parts = line.strip().split(",")

            if len(parts) != 5:
                continue

            name, date, amount, category, city = parts
            year, month, day = date.split("-")

            sales.append({
                "name": name,
                "date": date,
                "amount": int(amount),
                "category": category,
                "city": city,
                "year": year,
                "month": month,
                "day": day,
            })

    return sales


def write_category_reports(output_dir, grouped_sales):
    """Создаёт файлы с отчётами по категориям."""
    for (year, month, category), records in grouped_sales.items():
        category_file = os.path.join(output_dir, year, month, f"{category}.txt")
        os.makedirs(os.path.dirname(category_file), exist_ok=True)

        sorted_records = sorted(records, key=lambda x: x["date"])

        with open(category_file, "w", encoding="utf-8") as file:
            for record in sorted_records:
                file.write(f"{record['date']},{record['name']},{record['amount']}\n")


def write_monthly_reports(output_dir, category_totals):
    """Создаёт общий отчёт по каждому месяцу."""
    for (year, month), category_sums in category_totals.items():
        report_file = os.path.join(output_dir, year, month, "monthly_report.txt")
        os.makedirs(os.path.dirname(report_file), exist_ok=True)

        with open(report_file, "w", encoding="utf-8") as file:
            for category, total in sorted(category_sums.items()):
                file.write(f"{category},{total}\n")
            file.write(f"Full,{sum(category_sums.values())}\n")


def process_sales_data(file_path, output_dir):
    """Обрабатывает продажи и создаёт отчёты."""
    sales = read_sales_data(file_path)

    grouped_sales = defaultdict(list)
    category_totals = defaultdict(lambda: defaultdict(int))

    for record in sales:
        key = (record["year"], record["month"], record["category"])
        grouped_sales[key].append(record)
        category_totals[(record["year"], record["month"])][record["category"]] += record["amount"]

    write_category_reports(output_dir, grouped_sales)
    write_monthly_reports(output_dir, category_totals)


if len(sys.argv) < 3:
    print("Usage: python sales_report.py <input_file> <output_directory>")
    sys.exit(1)

input_file = sys.argv[1]
output_directory = sys.argv[2]

process_sales_data(input_file, output_directory)
print("Reports generated successfully!")

🧪 Как проверить решение

  1. Активируйте venv: venv\Scripts\activate (Windows) или source venv/bin/activate (Mac/Linux).
  2. Запустите файл: python sales_report.py sales_data.txt reports.
  3. Проверьте сценарии:
    • Создалась ли папка reports/ с вложенными папками YYYY/MM/.
    • В каждой папке есть monthly_report.txt с суммами и строкой Full.
    • Для каждой категории есть файл с записями, отсортированными по дате.

🐛 Отладка в VS Code

Если что-то не работает:

  1. Поставьте точку останова (breakpoint) в функции read_sales_data.
  2. Нажмите F5 или выберите «Run → Start Debugging».
  3. На панели Debug следите за значениями sys.argv, sales, grouped_sales и category_totals.
  4. Если программа падает с FileExistsError, проверьте, что используется exist_ok=True в os.makedirs.

💾 Сохранение работы

# Добавьте файлы урока
git add lesson-53/

# Сделайте коммит с осмысленным сообщением
git commit -m "lesson-53: анализ продаж по категориям и датам"

# Отправьте на GitHub / GitLab
git push

💡 Советы и типичные ошибки

  • Всегда указывайте encoding="utf-8" при открытии текстовых файлов.
  • Используйте os.path.join вместо ручного склеивания путей.
  • Не забывайте про exist_ok=True при создании папок.
  • Проверяйте len(sys.argv) перед использованием аргументов.
  • Не коммитьте папку venv/ и сгенерированные отчёты — они указаны в .gitignore.