Раздел 42 · Аналитика данных с нуля

CSV в Python: от файла до отчёта без pandas

junior~25 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Бран · продюсер «Хроник Пограничья»

Финансисты прислали CSV с покупками. В названии предмета есть запятая, у одной оплаты нет суммы. Мне нужен файл с выручкой по платформам. Только не потеряй строки и не запиши пропуск как ноль.

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

Сохрани шесть строк

В JupyterLab создай текстовый файл purchases_small.csv в одной папке с блокнотом. Вставь только содержимое блока ниже, без строк с тройными обратными кавычками. Сохрани файл в UTF-8.

id,platform,amount_rub,status,item_name
1,android,199,paid,Меч
2,ios,99,paid,"Зелье, большое"
3,android,199,refunded,Меч
4,android,0,paid,Подарок
5,ios,,paid,Неизвестная сумма
6,ios,50,paid,Монеты

В первой строке имена столбцов, в остальных шести покупки. В строке с id=5 между запятыми нет суммы: это пропуск. В строке с id=4 сумма явно равна нулю.

В CSV запятая внутри кавычек остаётся частью ячейки. Поэтому "Зелье, большое" это одно название, не два столбца. Обычное line.split(",") разрежет его неправильно: нужен разборщик CSV.

Где Python ищет файл

Запусти отдельную ячейку:

from pathlib import Path

folder = Path.cwd()
path = folder / "purchases_small.csv"
print(folder)
print(path.exists())

from pathlib import Path берёт инструмент Path из стандартного модуля Python, устанавливать его отдельно не нужно. Path.cwd() возвращает текущую рабочую папку процесса. В Jupyter она часто совпадает с папкой блокнота, но не обязана: проверяй напечатанный путь.

Оператор / между путём и именем файла соединяет части пути, а не делит числа. path.exists() проверяет наличие файла; ожидается True. Если получился False, сохрани CSV в напечатанную папку или задай точный путь, например Path("/Users/anna/reports/purchases_small.csv") на macOS. На Windows можно записать Path("C:/Users/Anna/reports/purchases_small.csv").

Path("purchases_small.csv") тоже работает, но относительный путь считается от текущей рабочей папки. При FileNotFoundError проверь имя, расширение и папку: расчёт тут ни при чём.

Прочитай словари, пока без чисел

Это продолжение ячейки, где определён path:

import csv

with open(path, newline="", encoding="utf-8") as file:
    reader = csv.DictReader(file)
    raw_rows = list(reader)

print(len(raw_rows))
print(raw_rows[1]["item_name"])
print(raw_rows[0]["amount_rub"])

Ответы: 6, Зелье, большое, 199. Последний ответ выглядит числом, но пока это текст. Проверка type(raw_rows[0]["amount_rub"]) покажет str.

По строкам:

  1. import csv подключает стандартный модуль CSV.
  2. open открывает файл для чтения. encoding="utf-8" задаёт кодировку, чтобы кириллица читалась одинаково на разных компьютерах.
  3. newline="" оставляет обработку переводов строк модулю CSV. Это важно и при чтении, и при записи.
  4. with ... as file: даёт открытому файлу имя file. Код с отступом работает с файлом, после выхода из блока файл закрывается, в том числе при ошибке.
  5. csv.DictReader(file) читает заголовок и превращает следующие записи в словари: имя столбца становится ключом.
  6. list(reader) собирает записи в список внутри блока with. После закрытия файла этот список можно обходить сколько угодно.

Здесь не нужен return: код стоит прямо в ячейке. В задаче ты поместишь чтение в функцию и вернёшь готовый список.

Строка «199» ещё не сумма

Продолжи после чтения файла:

rows = []
for raw in raw_rows:
    row = dict(raw)
    row["id"] = int(raw["id"])
    if raw["amount_rub"] == "":
        row["amount_rub"] = None
    else:
        row["amount_rub"] = float(raw["amount_rub"])
    rows.append(row)

print(rows[0]["amount_rub"] + rows[1]["amount_rub"])
print(rows[3]["amount_rub"])
print(rows[4]["amount_rub"])

Ответы: 298.0, 0.0, None. int превращает текст идентификатора в целое число, float превращает текст суммы в число с дробной частью. dict(raw) создаёт отдельную копию словаря: исходные текстовые строки остаются в raw_rows.

Пустую строку нельзя передать в float: получится ValueError. Условие обрабатывает известный пропуск явно. Текст "0" не пустой, он превращается в 0.0. Не заменяй все пустые суммы нулями: Бран иначе не заметит неполную выгрузку.

Для учебного отчёта float хватит. В точных финансовых расчётах используют целые копейки или Decimal, потому что двоичные дроби не всегда представляют десятичные суммы точно. Здесь все суммы целые, поэтому сверка на 348 ₽ не требует округления.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.