← назад к разделу

Всё, что было раньше, — инструменты. Теперь три задачи, ради которых их и брали.

в базе ord-01ord-02ord-03 в выгрузке ord-02ord-03ord-04 пропало: ord-01 лишнее: ord-04 было - стало и стало - было множества отвечают на это одной строкой, а в сообщение кладут сами номера, а не их количество

Разница двух наборов ищется вычитанием множеств: слева — чего не хватает, справа — что появилось лишнего.

Посчитать то, чего нет на экране

Интерфейс показывает список заказов, а вопрос звучит иначе: «на какую сумму заказов в статусе PAID за день?» Такой цифры на экране нет, а в данных — есть.

живой пример

заказы = [
    {"id": "ord-042", "status": "PAID", "total": 4990},
    {"id": "ord-043", "status": "CANCELLED", "total": 890},
    {"id": "ord-044", "status": "PAID", "total": 1780},
]

оплаченные = [з for з in заказы if з["status"] == "PAID"]
сумма = sum(з["total"] for з in оплаченные)

print(f"Оплаченных: {len(оплаченные)}, на сумму {сумма}")
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

sum() складывает числа из перечня. Рядом живут min(), max() и len() — этой четвёрки хватает для большинства сводок.

Считать по группам удобно словарём-накопителем:

живой пример

заказы = [{"status": "PAID"}, {"status": "CANCELLED"}, {"status": "PAID"}]

по_статусам = {}
for заказ in заказы:
    статус = заказ["status"]
    по_статусам[статус] = по_статусам.get(статус, 0) + 1

print(по_статусам)     # {'PAID': 2, 'CANCELLED': 1}
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

get(статус, 0) здесь и держит всю конструкцию: для нового статуса счётчика ещё нет, и вместо ошибки берётся ноль.

Отработать здесь: Проверить поле ответа · Сумма в отчёт · Проверить формат почты

Сверить итог с составом

Сумма позиций должна совпадать с итогом заказа. На экране такое расхождение не видно — его находят только счётом.

живой пример

# ошибка: пример падает намеренно — у второго заказа итог не сходится
заказы = [
    {"id": "ord-042", "items": [{"qty": 1, "price": 4990}], "total": 4990},
    {"id": "ord-043", "items": [{"qty": 2, "price": 890}], "total": 1000},
]

def сумма_сходится(заказ):
    посчитано = sum(п["qty"] * п["price"] for п in заказ["items"])
    return посчитано == заказ["total"]

расхождения = [з["id"] for з in заказы if not сумма_сходится(з)]
assert not расхождения, f"итог не сходится у заказов: {расхождения}"
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Главное в последней строке — что попадёт в сообщение об ошибке:

живой пример

расхождения = ["ord-042", "ord-107"]

# так не надо: видно, сколько заказов сломано, но не видно какие
print(f"итог не сходится у {len(расхождения)} заказов")

# так надо: по номерам заказ сразу открывают и смотрят
print(f"итог не сходится у заказов: {расхождения}")
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

С дробями осторожно: 0.1 + 0.2 в любом языке не равно ровно 0.3. Деньги поэтому считают либо в копейках целыми числами, либо сравнивают с допуском:

живой пример

# так не надо: дробные числа не сравнивают «в лоб»
print(0.1 + 0.2 == 0.3)

# так надо: разница меньше копейки — значит, сошлось
print(abs((0.1 + 0.2) - 0.3) < 0.01)
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Отработать здесь: Сходится ли итог заказа

Найти, что пропало и что лишнее

Две выгрузки: из интерфейса и из базы, из старой версии и из новой. Чего не хватает и что лишнее? Списками это неудобно, множествами — одна строка.

Множество — набор без порядка и без повторов:

живой пример

было = {"ord-01", "ord-02", "ord-03"}
стало = {"ord-02", "ord-03", "ord-04"}

print(было - стало)      # {'ord-01'}  — пропало
print(стало - было)      # {'ord-04'}  — появилось
print(было & стало)      # есть в обоих
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Отсюда и главная причина не сверять списками:

живой пример

# так не надо: порядок другой — значит, не равны, хотя состав тот же
print(["ord-01", "ord-02"] == ["ord-02", "ord-01"])

# так надо: множеству порядок безразличен
print({"ord-01", "ord-02"} == {"ord-02", "ord-01"})
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Из списка словарей множество собирают по ключевому полю:

живой пример

база = [{"id": "ord-042"}, {"id": "ord-043"}]
выгрузка = [{"id": "ord-042"}, {"id": "ord-043"}]

в_базе = {з["id"] for з in база}
в_выгрузке = {з["id"] for з in выгрузка}

пропали = в_базе - в_выгрузке
лишние = в_выгрузке - в_базе

assert not пропали, f"нет в выгрузке: {sorted(пропали)}"
assert not лишние, f"лишние в выгрузке: {sorted(лишние)}"
print("состав совпал")
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

sorted() в сообщении не украшение: у множества нет порядка, и без сортировки одно и то же расхождение печатается каждый раз по-разному.

Отработать здесь: Что пропало и что лишнее · Свести две выгрузки

Сравнить не только состав, но и содержимое

Совпадения номеров мало: заказ может быть на месте, но с другой суммой. Тогда сверяют поля:

живой пример

база = [{"id": "ord-042", "total": 4990}]
выгрузка = [{"id": "ord-042", "total": 4990}]

эталон = {з["id"]: з for з in база}     # словарь по номеру

for заказ in выгрузка:
    ожидаем = эталон[заказ["id"]]
    assert заказ["total"] == ожидаем["total"], (
        f"{заказ['id']}: в выгрузке {заказ['total']}, в базе {ожидаем['total']}")

print("суммы совпали")
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Строка со словарём эталон — приём, который стоит запомнить: список превращается в словарь по ключу, и дальше элемент достаётся сразу, без перебора.

Отработать здесь: Сверить ответ с ожидаемым

Коротко

  • sum(), len(), min(), max() закрывают большинство сводок; счёт по группам — словарь-накопитель с get(ключ, 0).
  • Сверка итога с составом ловит расхождения, невидимые на экране; в сообщение кладите номера проблемных записей, а не их количество.
  • Деньги в дробных числах сравнивают с допуском либо считают в копейках.
  • Разницу двух наборов ищут множествами: было - стало и стало - было; списки на такое сравнение не годятся — им важен порядок.
  • Список превращают в словарь по ключу, когда нужно быстро находить пару.

Что почитать дальше