📋 Формулировка задания
Исходное задание:
Напишите программу, которая обрабатывает текстовый файл с данными о продажах. Используйте файл sales_data.txt.
- Принимайте аргументы командной строки:
python sales_report.py <input_file> <output_directory>. - Читайте данные из файла, где каждая строка имеет формат
имя,дата,сумма,категория,город. - Сгруппируйте данные по годам и месяцам, создавая для каждого периода отдельную папку.
- Создайте
monthly_report.txtс суммарной выручкой по каждой категории и общей суммой. - Создайте файлы для каждой категории с записями
дата,имя продавца,сумма, отсортированными по дате.
🔧 Шаг 0. Подготовка окружения
Перед началом работы настройте рабочее окружение. Эти шаги одинаковы для всех уроков.
.gitignore, единая история, легко переключаться между уроками.
Создайте (или откройте) рабочую папку
В VS Code нажмите Ctrl+K Ctrl+O → выберите или создайте папку python-fundamentals-practice. Это будет корень вашего учебного репозитория.
# Или в терминале VS Code (Ctrl + `):
mkdir python-fundamentals-practice
cd python-fundamentals-practice
Инициализируйте 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: базовая структура репозитория"
Создайте папку для текущего урока
# Структура: lesson-XX/
mkdir -p lesson-53
cd lesson-53
Создайте виртуальное окружение (venv)
# Windows (PowerShell / CMD)
python -m venv venv
venv\Scripts\activate
# Mac / Linux
python3 -m venv venv
source venv/bin/activate
После активации в начале строки терминала появится (venv). Все дальнейшие команды выполняйте внутри venv.
Создайте ветку для задания
# Вернитесь в корень репозитория
cd ../..
# Создайте и переключитесь на ветку
git checkout -b lesson/53-practice-13
# Отправьте ветку на удалённый репозиторий (если настроен)
git push -u origin lesson/53-practice-13
Именование веток: lesson/XX-краткое-название. Это позволяет легко находить нужный урок.
🛠️ Шаги выполнения задания
Теперь, когда окружение готово, приступайте к решению.
Шаг 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: реализуйте чтение и парсинг
Создайте функцию 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: сгруппируйте данные
Используйте 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: создайте файлы отчётов
Напишите функции write_category_reports и write_monthly_reports. Используйте os.path.join для путей и os.makedirs(..., exist_ok=True) для создания папок.
Шаг 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!")
🧪 Как проверить решение
- Активируйте venv:
venv\Scripts\activate(Windows) илиsource venv/bin/activate(Mac/Linux). - Запустите файл:
python sales_report.py sales_data.txt reports. - Проверьте сценарии:
- Создалась ли папка
reports/с вложенными папкамиYYYY/MM/. - В каждой папке есть
monthly_report.txtс суммами и строкойFull. - Для каждой категории есть файл с записями, отсортированными по дате.
- Создалась ли папка
🐛 Отладка в VS Code
Если что-то не работает:
- Поставьте точку останова (breakpoint) в функции
read_sales_data. - Нажмите F5 или выберите «Run → Start Debugging».
- На панели Debug следите за значениями
sys.argv,sales,grouped_salesиcategory_totals. - Если программа падает с
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.