Mapowanie wartości w słowniku Pythona

243

Biorąc pod uwagę słownik, { k1: v1, k2: v2 ... }który chcę uzyskać, { k1: f(v1), k2: f(v2) ... }pod warunkiem, że podam funkcję f.

Czy jest taka wbudowana funkcja? Czy muszę to zrobić

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

Idealnie po prostu bym napisał

my_dictionary.map_values(f)

lub

my_dictionary.mutate_values_with(f)

Oznacza to, że nie ma dla mnie znaczenia, czy oryginalny słownik jest zmutowany, czy tworzona jest kopia.

Tarrasch
źródło
2
Lepszym sposobem napisania twojego przykładu byłoby dict((k, f(v)) for k, v in mydict.iteritems()), tj. Bez nawiasów kwadratowych, które uniemożliwiłyby utworzenie listy pośredniej za pomocą generatora.
bereal

Odpowiedzi:

354

Nie ma takiej funkcji; najłatwiej to zrobić, używając rozumienia dykta:

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

W Pythonie 2.7 użyj .iteritems()metody zamiast .items()oszczędzania pamięci. Składnia rozumienia dict została wprowadzona dopiero w Pythonie 2.7.

Zauważ, że nie ma takiej metody na listach; musiałbyś użyć rozumienia listy lub map()funkcji.

Jako taki, możesz również użyć map()funkcji do przetworzenia swojego nagrania:

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

ale tak naprawdę nie jest to tak czytelne.

Martijn Pieters
źródło
5
+1: to też bym zrobił. dict(zip(a, map(f, a.values())))jest nieznacznie krótszy, ale muszę pomyśleć o tym, co robi i przypomnieć sobie, że tak, klucze i wartości są iterowane w tej samej kolejności, jeśli dyktand się nie zmienia. Nie muszę wcale myśleć o tym, co robi dictcomp, więc jest to właściwa odpowiedź.
DSM
2
@chiborg: to dlatego, że zamiast sprawdzać wszystkie pary klucz-wartość za jednym razem, teraz używasz liczby my_dictionary.__getitem__połączeń razy razy .
Martijn Pieters
1
Zauważ, że ponieważ PEP3113 (zaimplementowany w Pythonie 3.x) parametry krotki nie są już obsługiwane: lambda (k,v): (k, f(v))należy przepisać na coś w stylulambda k_v: (k_v[0], f(k_v[1]))
normanius
1
Dlaczego rozpakowanie parametru zostało usunięte? Jak to jest poprawa ?
javadba
3
pochodzący z języka FP, Python wydawałby się niesamowicie niezręczny.
juanchito,
21

Możesz to zrobić w miejscu, zamiast tworzyć nowy słownik, co może być lepsze w przypadku dużych słowników (jeśli nie potrzebujesz kopii).

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)

skutkuje my_dictionaryzawartością:

{'a': 2, 'b': 3}
gens
źródło
1
Cool, należy może zmienić nazwę mapdictna mutate_values_withlub coś zrobić to jasne, że pan przepisać dict! :)
Tarrasch
2
zip(d.keys(), d.values())działa dla większej liczby wersji zamiastiteritems()
ytpillai
1
@ytpillai „zip” lub wyrażenia tworzą kopię, zamiast zmieniać wartości w miejscu, co jest celem mojej odpowiedzi. Zaakceptowana odpowiedź jest najlepsza, gdy kopia jest w porządku.
gens,
1
Przepraszam, nie zdawałem sobie sprawy, że chcesz użyć metody przedmiotów. Możliwe jest jednak również inne ulepszenie tego (dla użytkowników innych niż Python 2.7){k:f(v) for k,v in iter(d.items())}
ytpillai
1
Oszczędza miejsce, tworząc iterator
ytpillai
13

Ze względu na PEP-0469, który zmienił nazwę iteritems () na items () i PEP-3113, który usunął rozpakowywanie parametrów Tuple , w Pythonie 3.x powinieneś napisać Martijn Pieters ♦ odpowiedz w ten sposób:

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))
Lucidyan
źródło
4

Chociaż moja pierwotna odpowiedź nie trafiła w sedno (próbując rozwiązać ten problem z rozwiązaniem klucza dostępu w fabryce defaultdict ), przerobiłem go, aby zaproponować faktyczne rozwiązanie obecnego pytania.

Oto on:

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

Stosowanie:

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

Chodzi o podklasę oryginalnego słownika, aby zapewnić mu pożądaną funkcjonalność: „mapowanie” funkcji na wszystkie wartości.

Plusem jest to, że słownik ten może być używany do przechowywania oryginalnych danych, tak jakby to był dict, podczas przekształcania dowolnych danych na żądanie za pomocą wywołania zwrotnego.

Oczywiście możesz nazwać klasę i funkcję tak, jak chcesz (nazwa wybrana w tej odpowiedzi jest inspirowana array_walk()funkcją PHP ).

Uwaga: Ani try- exceptblok ani returnstwierdzenia są obowiązkowe dla funkcjonalności, są tam do dalszego naśladować zachowanie PHP array_walk.

7heo.tk
źródło
1
To nie rozwiązuje pytania OP, ponieważ __missing__metoda nie będzie wywoływana dla istniejących kluczy, które chcemy przekształcić, chyba że metoda fabryczna, która przeszła, używa w jakiś sposób dyktowania źródłowego jako rezerwowego, ale ponieważ nie jest to część przykładowego użycia, Uważam to za niezadowalającą odpowiedź na bieżący problem.
Kaos
Jakie istniejące klucze?
7heo.tk
Z OP: Given a dictionary { k1: v1, k2: v2 ... } .... To znaczy, musisz już dictzacząć ...
Kaos
Chciałbym powiedzieć, że oboje mamy rację; ale wierzę, że oboje się mylimy. Masz rację, ponieważ moja odpowiedź nie odpowiada na pytanie; ale nie z powodu, na który się powołałeś. Po prostu mijałem ten punkt, dając sposób na uzyskanie {v1: f(v1), v2: f(v2), ...}danego [v1, v2, ...], a nie dyktowania. Zmienię swoją odpowiedź, aby to poprawić.
7heo.tk
2

Aby uniknąć indeksowania od wewnątrz lambda, na przykład:

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

Możesz także:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
Z poważaniem
źródło
To sprytna manipulacja w samej krotce 2 w drugim przykładzie. Wykorzystuje jednak automatyczne rozpakowywanie krotek w ramach lambda, które nie jest już obsługiwane w Pythonie 3. Dlatego lambda(k,v)nie będzie działać. Zobacz stackoverflow.com/questions/21892989/...
Jonathan Komar,
0

Właśnie natknąłem się na ten przypadek użycia. Zaimplementowałem odpowiedź gensa , dodając rekurencyjne podejście do obsługi wartości, które również dyktują:

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)

Może to być przydatne w przypadku plików json lub yaml, które kodują ciągi jako bajty w Pythonie 2

Oyono
źródło