Отображение значений в словаре Python

243

Имея словарь, который { 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)

или

my_dictionary.mutate_values_with(f)

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

Тарраш
источник
2
Лучшим способом написания вашего примера было бы dict((k, f(v)) for k, v in mydict.iteritems()), то есть без квадратных скобок, который предотвратил бы создание промежуточного списка через генератор.
bereal

Ответы:

355

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

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

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

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

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

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

но это не так читаемо, правда.

Мартейн Питерс
источник
5
+1: это то, что я бы тоже сделал. dict(zip(a, map(f, a.values())))немного короче, но я должен подумать о том, что он делает, и напомнить себе, что да, ключи и значения перебираются в том же порядке, если dict не меняется. Мне совсем не нужно думать о том, что делает dictcomp, и это правильный ответ.
DSM
2
@chiborg: это потому, что вместо того, чтобы искать все пары ключ-значение за один раз, вы теперь используете my_dictionary.__getitem__вызовы количества ключей .
Мартин Питерс
1
Обратите внимание, что начиная с PEP3113 (реализовано в python 3.x), параметры кортежа больше не поддерживаются: lambda (k,v): (k, f(v))их следует переписать в нечто вродеlambda k_v: (k_v[0], f(k_v[1]))
normanius
1
Почему распаковка параметров была отменена? Как это улучшение ?
Джавадба
3
Исходя из языка FP, Python будет казаться невероятно неловким.
Хуанхито
22

Вы можете сделать это на месте, а не создавать новый 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}
Gens
источник
1
Круто, вы должны возможно переименовать mapdictв mutate_values_withили что - то , чтобы сделать его кристально ясно , что вы переписать Dict! :)
Tarrasch
2
zip(d.keys(), d.values())работает для большего количества версий вместоiteritems()
ytpillai
1
@ytpillai 'zip' или понимание делают копию, а не меняют значения на месте, что является целью моего ответа. Принятый ответ является лучшим, когда копия в порядке.
Gens
1
Мои извинения, я не осознавал, что вы хотели использовать метод предметов. Однако возможны и другие улучшения (для пользователей, не являющихся пользователями Python 2.7){k:f(v) for k,v in iter(d.items())}
ytpillai
1
Экономит место, создавая итератор
ytpillai
13

Из-за PEP-0469, который переименовал iteritems () в items (), и PEP-3113, который удалил распаковку параметров Tuple , в Python 3.x вы должны написать Martijn Pieters ♦ ответ так:

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))
lucidyan
источник
4

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

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

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

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

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

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

Вы также можете сделать:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
с уважением
источник
Это умная манипуляция внутри самого кортежа 2 во втором примере. Однако он использует автоматическую распаковку кортежей в лямбде, которая больше не поддерживается в Python 3. Поэтому lambda(k,v)не будет работать. См. Stackoverflow.com/questions/21892989/…
Джонатан Комар
0

Просто наткнулся на этот вариант использования. Я реализовал ответ 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

Ойоно
источник