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 можно вычислить без обращения к этой функции. Эта небольшая правка убирает лишнюю работу и делает преобразования данных компактными и надёжными.