Сопоставление значений в словаре Python

Учитывая словарь { k1: v1, k2: v2 ... }, я хочу получить { k1: f(v1), k2: f(v2) ... } при условии, что я передаю функцию f.

Есть ли такая встроенная функция? Или мне нужно сделать

dict([(k, f(v)) for (k, v) in my_dictionary.iteritems()])

В идеале я бы просто написал

my_dictionary.map_values(f)

or

my_dictionary.mutate_values_with(f)

То есть для меня не имеет значения, был ли изменен исходный словарь или создана его копия.


person Tarrasch    schedule 01.09.2012    source источник
comment
Лучшим способом написания вашего примера было бы dict((k, f(v)) for k, v in mydict.iteritems()), то есть без квадратных скобок, что предотвратило бы создание промежуточного списка через генератор.   -  person bereal    schedule 01.09.2012


Ответы (7)


Такой функции нет; Самый простой способ сделать это - использовать понимание dict:

my_dictionary = {k: f(v) for k, v in my_dictionary.items()}

В python 2.7 используйте метод .iteritems() вместо .items() для экономии памяти. Синтаксис понимания dict не был введен до Python 2.7.

Обратите внимание, что в списках такого метода тоже нет; вам придется использовать понимание списка или функцию map().

Таким образом, вы также можете использовать функцию map() для обработки своего dict:

my_dictionary = dict(map(lambda kv: (kv[0], f(kv[1])), my_dictionary.iteritems()))

но на самом деле это не так удобно.

person Martijn Pieters    schedule 01.09.2012
comment
+1: я бы тоже так поступил. dict(zip(a, map(f, a.values()))) немного короче, но я должен подумать о том, что он делает, и напомнить себе, что да, ключи и значения повторяются в том же порядке, если dict не меняется. Мне вообще не нужно думать о том, что делает dictcomp, так что это правильный ответ. - person DSM; 01.09.2012
comment
@DSM: Да, трюк zip(adict, map(f, adict.values()))) требует слишком большого понимания от обычного читателя кода, не говоря уже о твердой руке в добавлении всех закрывающих параметров! :-П - person Martijn Pieters; 01.09.2012
comment
{k: f(my_dictionary[k]) for k in my_dictionary} немного короче, но, что интересно, также немного медленнее (при синхронизации с timeit, dict из 500 элементов и str() для f). Не знаю почему. - person chiborg; 15.10.2014
comment
@chiborg: это потому, что вместо того, чтобы искать все пары ключ-значение за один раз, вы теперь используете количество ключей, умноженное на my_dictionary.__getitem__ вызовы. - person Martijn Pieters; 15.10.2014
comment
Обратите внимание: поскольку PEP3113 (реализованный в python 3.x) параметры кортежа являются больше не поддерживается: lambda (k,v): (k, f(v)) следует переписать на что-то вроде lambda k_v: (k_v[0], f(k_v[1])) - person normanius; 13.01.2018
comment
@normanius: спасибо за предупреждение. Да, я знаю, что распаковка параметров отсутствует в Py3, но пока поиск в Stack Overflow не станет намного лучше, я не знаю, в каких ответах я использовал синтаксис. :-) - person Martijn Pieters; 13.01.2018
comment
Почему отключена распаковка параметров? Как это улучшение? - person WestCoastProjects; 18.02.2018
comment
Итак, я прочитал pep. По крайней мере, это признается: это не улучшение как таковое (напротив…), а признание ограничений в инструментах для вывода правильных типов tuple аргументов. - person WestCoastProjects; 18.02.2018
comment
Исходя из языка FP, Python мог бы показаться невероятно неудобным. - person juanchito; 09.05.2018
comment
Иногда вам нужно применить функцию и к ключу, и к значению. Например, вы хотите преобразовать ключ и значение, используя одни и те же сгенерированные данные (фейкер). Каждая пара должна иметь свои собственные данные. В этом случае я не вижу возможности использовать dict-понимание, только map. - person x-yuri; 18.06.2019
comment
@ x-yuri, так что объедините их. Сопоставьте свой итеративный ввод с поддельными объектами, а затем используйте dict comp для создания пар ключ-значение dict. - person Martijn Pieters; 23.06.2019
comment
@MartijnPieters Не все так просто. Мой итеративный ввод - это dict. Я хочу преобразовать его в другой dict (как ключи, так и значения), используя данные, созданные faker. Для каждой пары ключ / значение необходимо один раз создать фиктивный объект. Ключ - это тестовое входное значение, значение ожидаемого результата. И ключ, и значение выражаются с помощью небольшого языка. Мол, "s" нужно заменить предложением. Так что нет места для понимания слов. - person x-yuri; 24.06.2019
comment
@ x-yuri, конечно, есть. Однако похоже, что вы все равно перегружали словарь слишком большим количеством значений. - person Martijn Pieters; 24.06.2019
comment
У меня есть функция, которая преобразует двойные символы новой строки в абзацы, например s\n\ns - ›<p>s<p>s, s\r\rs -› <p>s<p>s и так далее. Где s - предложение. Как выгрузить словарь или как использовать понимание словаря, когда ключ и значение должны иметь доступ к одним и тем же случайным значениям (предложениям, используемым в этом тесте)? - person x-yuri; 24.06.2019
comment
@ x-yuri: комментарии уже выходят из-под контроля. processed = map(nl_to_para, faked_data), result = {key_expr: value_expr for text in processed}. Вы можете поместить вызов map() в понимание dict (замените in processed на in map(...)). Вы можете заменить map() выражением генератора. И т.д. Итерация расширяема. - person Martijn Pieters; 24.06.2019
comment
Я надеюсь, что отображение кода прояснит проблему. Я не вижу способа избежать использования map. Но если он есть, держу пари, его сложнее понять. Тогда я не уверен, как люди обычно это делают, но вот ссылка на комнату, чтобы обсудить это дополнительно (при необходимости): chat.stackoverflow.com/rooms/195480/ - person x-yuri; 25.06.2019

Эти инструменты отлично подходят для такой простой, но повторяющейся логики.

http://toolz.readthedocs.org/en/latest/api.html#toolz.dicttoolz.valmap

Доставит вас там, где вы хотите быть.

import toolz
def f(x):
  return x+1

toolz.valmap(f, my_list)
person Jesse Smith    schedule 15.05.2014

Вы можете сделать это на месте, а не создавать новый dict, что может быть предпочтительнее для больших словарей (если вам не нужна копия).

def mutate_dict(f,d):
    for k, v in d.iteritems():
        d[k] = f(v)

my_dictionary = {'a':1, 'b':2}
mutate_dict(lambda x: x+1, my_dictionary)

приводит к my_dictionary, содержащему:

{'a': 2, 'b': 3}
person gens    schedule 24.08.2014
comment
Круто, вам, возможно, стоит переименовать mapdict в mutate_values_with или что-то еще, чтобы было кристально ясно, что вы переписываете dict! :) - person Tarrasch; 24.08.2014
comment
zip(d.keys(), d.values()) работает для других версий вместо iteritems() - person ytpillai; 07.08.2015
comment
Кроме того, вместо цикла for вы можете сделать что-то похожее на то, что было сделано в предыдущем комментарии в другом ответе, dict(zip(d.keys(), [f(v) for v in dict.values()])) - person ytpillai; 07.08.2015
comment
Или понимание слов (не уверен, работает ли он во всех версиях) {k:f(v) for k,v in zip(d.keys(), d.values())} - person ytpillai; 07.08.2015
comment
@ytpillai 'zip' или понимания делают копию, а не изменяют значения на месте, что является целью моего ответа. Принятый ответ - лучший вариант, если с копией все в порядке. - person gens; 10.08.2015
comment
Приношу свои извинения, я не осознавал, что вы хотите использовать метод предметов. Однако возможно и другое улучшение (для пользователей, отличных от Python 2.7) {k:f(v) for k,v in iter(d.items())} - person ytpillai; 10.08.2015
comment
Экономит место, делая итератор - person ytpillai; 10.08.2015

Из-за PEP-0469, который переименовал iteritems () в items () и < href = "http://legacy.python.org/dev/peps/pep-3113/" rel = "noreferrer"> PEP-3113, в котором удалена распаковка параметра кортежа в Python 3.x вам следует написать Мартин Питерс ♦ ответ следующим образом:

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))
person lucidyan    schedule 22.09.2016

Хотя в моем исходном ответе не было смысла (пытаясь решить эту проблему с помощью решения для доступа к ключу на заводе defaultdict), я переработали его, чтобы предложить реальное решение данного вопроса.

Вот:

class walkableDict(dict):
  def walk(self, callback):
    try:
      for key in self:
        self[key] = callback(self[key])
    except TypeError:
      return False
    return True

Использование:

>>> d = walkableDict({ k1: v1, k2: v2 ... })
>>> d.walk(f)

Идея состоит в том, чтобы создать подкласс исходного dict, чтобы придать ему желаемую функциональность: «сопоставить» функцию со всеми значениями.

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

Конечно, не стесняйтесь называть класс и функцию так, как вы хотите (имя, выбранное в этом ответе, вдохновлено PHP _ 4_).

Примечание. Ни блок _5 _-_ 6_, ни операторы return не являются обязательными для функциональности, они предназначены для дальнейшего имитации поведения PHP array_walk.

person 7heo.tk    schedule 07.05.2015
comment
Это не решает вопрос OP, поскольку метод __missing__ не будет вызываться для существующих ключей, которые мы хотим преобразовать, если только переданный фабричный метод каким-то образом не использует исходный dict в качестве запасного варианта, но поскольку это не является частью использования примера , Я считаю это неудовлетворительным ответом на поставленную задачу. - person Kaos; 10.02.2016
comment
Какие существующие ключи? - person 7heo.tk; 19.05.2016
comment
Из ОП: Given a dictionary { k1: v1, k2: v2 ... } .... То есть у вас уже есть dict для начала .. - person Kaos; 20.05.2016
comment
Хочу сказать, что мы оба правы; но я считаю, что мы оба ошибаемся. Вы правы в том, что мой ответ не отвечает на вопрос; но не по той причине, которую вы вызвали. Я просто упустил суть, дав возможность получить {v1: f(v1), v2: f(v2), ...} с учетом [v1, v2, ...], а не с указанием. Я отредактирую свой ответ, чтобы исправить это. - person 7heo.tk; 20.05.2016

Чтобы избежать индексации изнутри лямбда, например:

rval = dict(map(lambda kv : (kv[0], ' '.join(kv[1])), rval.iteritems()))

Вы также можете:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
person yourstruly    schedule 29.03.2019
comment
Это умная манипуляция с двумя кортежами во втором примере. Однако он использует автоматическую распаковку кортежей в лямбда-выражении, которая больше не поддерживается в Python 3. Следовательно, lambda(k,v) не будет работать. См. stackoverflow.com/questions/21892989/ - person Jonathan Komar; 04.12.2019

Только что наткнулся на этот вариант использования. Я реализовал ответ gens, добавив рекурсивный подход для обработки значений, которые также являются dicts:

def mutate_dict_in_place(f, d):
    for k, v in d.iteritems():
        if isinstance(v, dict):
            mutate_dict_in_place(f, v)
        else:
            d[k] = f(v)

# Exemple handy usage
def utf8_everywhere(d):
    mutate_dict_in_place((
        lambda value:
            value.decode('utf-8')
            if isinstance(value, bytes)
            else value
        ),
        d
    )

my_dict = {'a': b'byte1', 'b': {'c': b'byte2', 'd': b'byte3'}}
utf8_everywhere(my_dict)
print(my_dict)

Это может быть полезно при работе с файлами json или yaml, которые кодируют строки как байты в Python 2.

person Oyono    schedule 06.04.2018