Урок 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.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исходный каталог?