Перейти к содержанию

Урок 004: каталог как список словарей

Цель

Объединить списки, словари, строки и функции в маленькую модель каталога: добавлять уникальные позиции, искать, фильтровать, сортировать и рассчитывать сводку.

Структура каталога

catalog = [
    {
        "article": "LO100",
        "name": "Лоток перфорированный",
        "quantity": 3,
        "unit_price": 1250.0,
    },
    {
        "article": "CB200",
        "name": "Кабель силовой",
        "quantity": 20,
        "unit_price": 180.0,
    },
]

Список отвечает за порядок и количество записей. Каждый словарь связывает поля одной позиции. Договоритесь об обязательных ключах и одинаковых типах во всех записях.

Сохраните каталог в catalog_lab.py. Фрагменты этого урока последовательно дополняют один файл: при отдельном запуске переносите также определения, от которых зависит пример.

Поиск по артикулу

def normalize_article(value):
    return "".join(value.split()).upper()


def find_by_article(catalog, article):
    normalized = normalize_article(article)

    for position in catalog:
        stored_article = normalize_article(position["article"])

        if stored_article == normalized:
            return position

    return None

None означает отсутствие результата. Проверяйте его явно:

found = find_by_article(catalog, " lo 100 ")

if found is None:
    print("Позиция не найдена")
else:
    print(found["name"])

Добавление без дублей

REQUIRED_FIELDS = {"article", "name", "quantity", "unit_price"}


def missing_fields(position):
    return sorted(REQUIRED_FIELDS.difference(position))


def add_position(catalog, position):
    if missing_fields(position):
        return False

    stored_position = position.copy()
    stored_position["article"] = normalize_article(stored_position["article"])
    stored_position["name"] = stored_position["name"].strip()

    if stored_position["article"] == "" or stored_position["name"] == "":
        return False

    if find_by_article(catalog, stored_position["article"]) is not None:
        return False

    catalog.append(stored_position)
    return True

Функция изменяет переданный список и сообщает результат логическим значением. Перед сохранением она проверяет схему, нормализует обязательные строки и создаёт неглубокую копию словаря. Поэтому последующее присваивание position["name"] = ... снаружи не меняет уже добавленную запись каталога.

Полный исполняемый вариант этих функций находится в примере catalog_core.py, а ожидаемое поведение закреплено в поведенческих тестах. Сначала реализуйте функции самостоятельно, затем используйте пример для сравнения, а не для слепого копирования.

Фильтрация по названию

def search_by_name(catalog, query):
    normalized_query = query.strip().casefold()

    if normalized_query == "":
        return []

    matches = []

    for position in catalog:
        if normalized_query in position["name"].casefold():
            matches.append(position)

    return matches

Возвращается новый список, но словари внутри него остаются теми же объектами. Изменение найденного словаря изменит запись исходного каталога.

Сводка

def catalog_summary(catalog):
    line_count = len(catalog)
    unit_count = 0
    total_cost = 0

    for position in catalog:
        unit_count += position["quantity"]
        total_cost += position["quantity"] * position["unit_price"]

    return line_count, unit_count, total_cost

Не путайте количество строк каталога с суммой единиц. Две позиции по десять штук дают line_count = 2 и unit_count = 20.

Сортировка

sorted_catalog = sorted(
    catalog,
    key=lambda position: position["name"].casefold(),
)

sorted возвращает новый список. Метод catalog.sort(...) меняет исходный. В учебной бизнес-логике предпочтительнее не менять порядок без явной необходимости.

lambda здесь является короткой функцией, возвращающей ключ сортировки. Эквивалент с именованной функцией:

def name_key(position):
    return position["name"].casefold()


sorted_catalog = sorted(catalog, key=name_key)

Контрольный набор

Для двух позиций из первого примера:

  • строк каталога: 2;
  • единиц: 3 + 20 = 23;
  • стоимость: 3 × 1250 + 20 × 180 = 7350 рублей.

Ручной расчёт нужен до запуска функции.

Упражнение

Соберите каталог из трёх словарей. Реализуйте проверку обязательных полей, поиск по артикулу, поиск по части названия, добавление без дубля, сортировку по названию и сводку. Проверьте найденную позицию, отсутствующий артикул, неполный словарь, пустой запрос, дублирующий артикул и каталог с нулевым количеством.

Самопроверка

  • Почему find_by_article возвращает None?
  • Какой побочный эффект есть у add_position?
  • Зачем перед сохранением создаётся копия словаря?
  • Чем число строк отличается от количества единиц?
  • Меняет ли sorted исходный каталог?

← Предыдущий урок · Практика →