Решён
Как найти в списке словарей Python нужное значение?

Иван Смирнов Python
3.6k
7

Есть список словарей вот такого вида:

users = [
    {"id": 1, "name": "Alice", "age": 30},
    {"id": 2, "name": "Bob", "age": 25},
    {"id": 3, "name": "Charlie", "age": 35}
]

Нужно найти словарь, где name == "Bob". Пробовал через обычный цикл for, работает, но выглядит громоздко. Есть более питоничный способ? И как быть если таких записей может быть несколько - вернуть все или только первую?

Решение
67
Эксперт • 2 ответа

Если нужна только первая запись - next с генератором:

result = next((u for u in users if u["name"] == "Bob"), None)

Вернет None если не найдет. Если нужны все совпадения:

results = [u for u in users if u["name"] == "Bob"]
Аватар Иван Смирнов

Именно то что нужно. `next` с дефолтом - элегантно, спасибо.

34
Участник • 2 ответа

Разберу подробнее, потому что у каждого подхода своя семантика.

Вариант 1 - list comprehension (все совпадения):

results = [u for u in users if u["name"] == "Bob"]

Просматривает весь список целиком. Возвращает новый список, пустой если ничего нет.

Вариант 2 - генераторное выражение плюс next (первое совпадение, ленивое):

result = next((u for u in users if u["name"] == "Bob"), None)

Останавливается как только нашел первый элемент. На больших списках заметно быстрее если нужна только одна запись.

Вариант 3 - filter:

result = list(filter(lambda u: u["name"] == "Bob", users))

Функционально то же самое что list comprehension, но медленнее из за lambda и читается хуже. В Python 3 filter возвращает итератор, не список - поэтому нужен list().

Для продакшен кода с большими объемами данных лучше смотреть в сторону pandas DataFrame или sqlalchemy - там поиск по полям через индексы, а не полный перебор.

28
Эксперт • 2 ответа

Кстати если у вас этот список по сути таблица с уникальными id и поиск идет часто - перестройте структуру в словарь словарей:

users_by_name = {u["name"]: u for u in users}
result = users_by_name.get("Bob")

Поиск O(1) вместо O(n). Один раз построили - дальше ищем мгновенно сколько угодно раз.

5
Эксперт • 1 ответ

можно еще так, через for else:

for u in users:
    if u["name"] == "Bob":
        result = u
        break
else:
    result = None

Не особо питонично но работает и логику понять проще чем next с генератором

19
Участник • 1 ответ

Обратите внимание что обращение u["name"] кинет KeyError если в каком то словаре нет ключа name. Безопаснее:

result = next((u for u in users if u.get("name") == "Bob"), None)

get вернет None для отсутствующего ключа вместо исключения. Мелочь, но в реальных данных такие дыры встречаются.

11
Эксперт • 1 ответ

Если данные приходят откуда то снаружи и структура нестабильна, то вместо голых словарей посмотрите на dataclasses или pydantic модели. Тогда поиск через list comprehension по атрибуту выглядит чище и IDE помогает с автодополнением:

from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    age: int

users = [User(1, "Alice", 30), User(2, "Bob", 25)]
result = next((u for u in users if u.name == "Bob"), None)
8
Участник • 1 ответ

У меня такая же задача была, только список на 200к записей. next с генератором работал нормально пока не понадобились частые поиски в цикле - тогда все встало. Пересел на pandas, .query() или булеву маску, стало в 50 раз быстрее.

Написать ответ

Премодерация гостей

Вы отвечаете как гость. Ваш ответ будет скрыт до проверки модератором. Чтобы ответ появился сразу и вы получали репутацию — войдите в аккаунт.

Будьте вежливы и соблюдайте правила платформы.