2025, Dec 19 18:02

Как построить dict в Python из кортежей с одним вызовом функции

Как построить словарь в Python из кортежей без двойных вызовов функции: dict(generator) и map. Почему dict comprehension тут неуместен и как сделать один проход

Преобразовать последовательность в словарь просто, когда у вас уже есть пары ключ–значение. Сложнее, когда каждую пару возвращает функция, отдающая ключ и значение вместе — особенно если она дорогая, имеет состояние или работает удалённо. В таких условиях вызывать её дважды для каждого элемента в словарном включении (dict comprehension) нельзя. Вот аккуратный способ получить k: v из одного вызова на элемент.

Постановка задачи

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

def kv_pair(txt):             # заглушка для ресурсоёмной или имеющей состояние функции
    return txt[0], txt[1:]

print([kv_pair(x) for x in ['abcd', 'efg', 'hi']])
print({kv_pair(x) for x in ['abcd', 'efg', 'hi']})
print({kv_pair(x)[0]: kv_pair(x)[1] for x in ['abcd', 'efg', 'hi']})

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

Почему словарное включение ведёт себя неправильно

В обычном словарном включении вида k: v for item in data выражения для ключа и значения вычисляются независимо. Если написать kv_pair(x)[0] в ключе и kv_pair(x)[1] в значении, произойдут два отдельных вызова. Для дорогой, имеющей состояние или удалённой функции это означает лишнюю работу и риск несогласованных результатов.

Решение: строим словарь из итерируемого с кортежами

Конструктор dict принимает итерируемый объект из двухэлементных кортежей, где каждый кортеж трактуется как ключ и значение. Так можно вызывать функцию ровно один раз на элемент и передавать её кортеж напрямую в dict. Самый простой шаблон — генераторное выражение:

print(dict(kv_pair(x) for x in ['abcd', 'efg', 'hi']))

Здесь kv_pair(x) вычисляется один раз на элемент, выдаёт кортежи вроде ('a', 'bcd'), а dict превращает их в {'a': 'bcd', ...}. Компактный эквивалент — через map с тем же результатом:

print(dict(map(kv_pair, ['abcd', 'efg', 'hi'])))

Хотите посмотреть онлайн — вот рабочая демонстрация: https://www.online-python.com/ME7cP0L6Hw.

Когда функция вообще не нужна

Если ключ и значение уже можно получить из самого входа, собирайте их прямо в включении, не вызывая дополнительную функцию. Например, когда отображение — это «первая буква → остаток строки», структура очевидна и не требует лишнего посредника.

Почему это важно

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

Итог

Если одна функция возвращает и ключ, и значение, направляйте её результат напрямую в dict. Используйте dict(kv_pair(x) for x in data) или dict(map(kv_pair, data)), чтобы гарантировать один вызов на элемент. А прямое формирование ключа и значения внутри включения оставьте для случаев, когда k и v можно вычислить без обращения к этой функции. Эта небольшая правка убирает лишнюю работу и делает преобразования данных компактными и надёжными.