CSV
Теория
CSV — простой табличный формат: одна строка — одна запись, значения разделены запятой (или точкой с запятой). Его открывает Excel, принимают бухгалтерские программы, в нём поставщики присылают прайсы, а маркетплейсы — заказы. Поэтому выгрузки и импорт в backend часто — это CSV.
Выглядит просто, и возникает соблазн разбирать CSV через line.split(","). Так делать нельзя: значение может содержать запятую или перевод строки — тогда его берут в кавычки ("Мышь, беспроводная"), и split разрежет его пополам. Модуль csv знает все эти правила. csv.DictReader читает строки как словари по заголовку, csv.DictWriter пишет словари. Важно помнить: все значения из CSV — строки, числа и цены нужно приводить самому. Файлы для модуля csv открывают с newline="" — модуль сам управляет переводами строк, иначе на Windows появятся пустые строки между записями. Русский Excel по умолчанию сохраняет CSV с разделителем ; — его указывают параметром delimiter.
Наглядно
Таблица 1. Инструменты модуля csv.
| Инструмент | Что делает | Строка выглядит как |
|---|---|---|
csv.reader(f) |
читает строки как списки | ['42', 'Мышь', '1499.90'] |
csv.DictReader(f) |
читает строки как словари по заголовку | {'id': '42', 'name': 'Мышь', 'price': '1499.90'} |
csv.writer(f).writerow(list) |
пишет список как строку | |
csv.DictWriter(f, fieldnames=[...]) |
пишет словари; writeheader(), writerows(rows) |
|
delimiter=";" |
другой разделитель | для CSV из русского Excel |
open(..., newline="", encoding="utf-8") |
так открывают файлы для csv |
Таблица 2. Почему не split(",").
| Строка файла | split(",") |
csv |
|---|---|---|
42,Мышь,1499.90 |
3 значения | 3 значения |
43,"Мышь, беспроводная",1990.00 |
4 значения — название разрезано | 3 значения |
44,"Коврик ""Pro""",490.00 |
кавычки остались в тексте | Коврик "Pro" |
На практике
Задача: прочитать прайс поставщика, посчитать выручку по выгрузке заказов и сохранить отчёт для бухгалтерии.
Шаг 1. Видим, почему split не подходит
Прайс поставщика, в одном названии есть запятая. Файл price.csv:
id,name,price 42,Мышь,1499.90 43,"Мышь, беспроводная",1990.00
Сравним наивный разбор и модуль csv:
import csv with open("price.csv", encoding="utf-8") as f: lines = f.read().splitlines() print(lines[2].split(",")) with open("price.csv", newline="", encoding="utf-8") as f: rows = list(csv.reader(f)) print(rows[2])
Результат:
['43', '"Мышь', ' беспроводная"', '1990.00'] ['43', 'Мышь, беспроводная', '1990.00']
Шаг 2. Читаем строки как словари
DictReader берёт заголовок из первой строки и отдаёт каждую запись словарём — к полям обращаемся по имени:
with open("price.csv", newline="", encoding="utf-8") as f: for row in csv.DictReader(f): print(row["id"], row["name"], row["price"], type(row["price"]))
Результат:
42 Мышь 1499.90 <class 'str'> 43 Мышь, беспроводная 1990.00 <class 'str'>
Цена — строка. Для расчётов её приводят: Decimal(row["price"]).
Шаг 3. Считаем выручку
Выгрузка заказов из маркетплейса. Файл orders.csv:
order_id,status,total 1042,paid,1499.90 1043,cancelled,5990.00 1044,paid,490.00
Суммируем оплаченные заказы, приводя строки к Decimal:
from decimal import Decimal with open("orders.csv", newline="", encoding="utf-8") as f: revenue = sum(Decimal(row["total"]) for row in csv.DictReader(f) if row["status"] == "paid") print(revenue)
Результат:
1989.90
Файл читается построчно — так же можно обработать выгрузку на миллион строк.
Шаг 4. Записываем отчёт
DictWriter пишет словари; заголовок — writeheader(). Файл открываем с newline="":
report = [
{"status": "paid", "count": 2, "revenue": "1989.90"},
{"status": "cancelled", "count": 1, "revenue": "0"},
]
with open("report.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["status", "count", "revenue"])
writer.writeheader()
writer.writerows(report)
with open("report.csv", encoding="utf-8") as f:
print(f.read(), end="")Результат:
status,count,revenue paid,2,1989.90 cancelled,1,0
Шаг 5. Читаем CSV из русского Excel
Excel с русскими настройками сохраняет CSV с точкой с запятой. Файл excel.csv:
id;name;price 42;Мышь;1499,90
С разделителем по умолчанию вся строка станет одним полем — указываем delimiter=";":
with open("excel.csv", newline="", encoding="utf-8") as f: print(next(csv.DictReader(f))) with open("excel.csv", newline="", encoding="utf-8") as f: row = next(csv.DictReader(f, delimiter=";")) print(row, Decimal(row["price"].replace(",", ".")))
Результат:
{'id;name;price': '42;Мышь;1499', None: ['90']}
{'id': '42', 'name': 'Мышь', 'price': '1499,90'} 1499.90В таком файле и дробная часть отделена запятой — перед Decimal её меняют на точку.
Частые ошибки
split(",")вместо модуляcsv. Запятые и кавычки внутри значений разрезают поля.- Значения как числа. Всё из CSV — строки:
sum(row["total"] ...)падает сTypeError. Приводите явно:int(...),Decimal(...). - Не тот разделитель. CSV из русского Excel — с
;; с разделителем по умолчанию вся строка попадёт в одно поле. - Без
newline="". На Windows между записями появляются пустые строки, а переводы строк внутри значений ломаются. floatдля денег. Как и везде, цены из CSV — вDecimal.
Итог
Чтение
csv.reader(f)— строки списками,csv.DictReader(f)— словарями по заголовку.- Файл:
open(путь, newline="", encoding="utf-8"); читается построчно — подходит для больших выгрузок. - Все значения — строки; числа и цены приводят сами.
Запись
csv.DictWriter(f, fieldnames=[...]),writeheader(),writerows(rows).
Форматы
- Значения с запятой или кавычками — модуль
csvэкранирует и разбирает их сам. delimiter=";"— для CSV из русского Excel; там же дробная часть может быть с запятой.
Вопросы на собеседовании
Почему CSV нельзя разбирать через split(",")?
Значения могут содержать разделитель, кавычки и переводы строк — тогда они берутся в кавычки, а кавычки внутри удваиваются. split этих правил не знает и режет значения. Модуль csv реализует формат правильно.
Зачем открывать файл с newline="" для модуля csv?
Модуль csv сам обрабатывает переводы строк, в том числе внутри значений в кавычках. Без newline="" Python преобразует переводы строк сам, и на Windows при записи появляются лишние пустые строки, а при чтении ломаются многострочные значения.
Как отдать пользователю CSV на миллион строк?
Не собирая его целиком в памяти: писать построчно генератором и отдавать потоком. В Django для этого есть StreamingHttpResponse с генератором строк, сформированных через csv.writer.