Zuordnung von Werten in einem Python-Wörterbuch

243

Wenn { k1: v1, k2: v2 ... }ich ein Wörterbuch habe, das ich erhalten möchte, übergebe { k1: f(v1), k2: f(v2) ... }ich eine Funktion f.

Gibt es eine solche eingebaute Funktion? Oder muss ich tun

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

Im Idealfall würde ich einfach schreiben

my_dictionary.map_values(f)

oder

my_dictionary.mutate_values_with(f)

Das heißt, es ist mir egal, ob das ursprüngliche Wörterbuch mutiert oder eine Kopie erstellt wird.

Tarrasch
quelle
2
Eine bessere Art, Ihr Beispiel zu schreiben, wäre dict((k, f(v)) for k, v in mydict.iteritems()), ohne die eckigen Klammern, die Erstellung einer Zwischenliste über einen Generator zu verhindern.
Bereal

Antworten:

354

Es gibt keine solche Funktion; Der einfachste Weg, dies zu tun, besteht darin, ein Diktatverständnis zu verwenden:

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

Verwenden Sie in Python 2.7 die .iteritems()Methode .items(), um Speicherplatz zu sparen. Die Diktatverständnis-Syntax wurde erst in Python 2.7 eingeführt.

Beachten Sie, dass es in Listen auch keine solche Methode gibt. Sie müssten ein Listenverständnis oder die map()Funktion verwenden.

Daher können Sie die map()Funktion auch zur Verarbeitung Ihres Diktats verwenden:

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

aber das ist wirklich nicht so lesbar.

Martijn Pieters
quelle
5
+1: das würde ich auch tun. dict(zip(a, map(f, a.values())))ist geringfügig kürzer, aber ich muss darüber nachdenken, was es tut, und mich daran erinnern, dass Schlüssel und Werte in derselben Reihenfolge wiederholt werden, wenn sich das Diktat nicht ändert. Ich muss überhaupt nicht darüber nachdenken, was der Diktiercomputer tut, und deshalb ist es die richtige Antwort.
DSM
2
@chiborg: Das liegt daran, dass Sie jetzt nicht mehr alle Schlüssel-Wert-Paare auf einmal nachschlagen, sondern die Anzahl der Schlüssel mal my_dictionary.__getitem__Anrufe verwenden.
Martijn Pieters
1
Beachten Sie, dass seit PEP3113 (in Python 3.x implementiert) Tupelparameter nicht mehr unterstützt werden: lambda (k,v): (k, f(v))lambda k_v: (k_v[0], f(k_v[1]))
Wird
1
Warum wurde das Entpacken von Parametern nixed? Wie ist das eine Verbesserung ?
Javadba
3
Python kommt aus einer FP-Sprache und scheint unglaublich umständlich.
Juanchito
21

Sie können dies direkt tun, anstatt ein neues Diktat zu erstellen, was für große Wörterbücher vorzuziehen ist (wenn Sie keine Kopie benötigen).

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)

führt zu folgenden my_dictionary:

{'a': 2, 'b': 3}
gens
quelle
1
Cool, sollten Sie vielleicht umbenennen mapdictzu mutate_values_withoder etwas , um es Kristall deutlich zu machen , dass Sie die dict umschreiben! :)
Tarrasch
2
zip(d.keys(), d.values())funktioniert für mehr Versionen anstelle voniteritems()
ytpillai
1
@ytpillai 'zip' oder Verständnis machen eine Kopie, anstatt die Werte an Ort und Stelle zu ändern, was der Zweck meiner Antwort ist. Die akzeptierte Antwort ist die beste, wenn eine Kopie in Ordnung ist.
Gens
1
Ich entschuldige mich, ich wusste nicht, dass Sie die Items-Methode verwenden wollten. Eine weitere Verbesserung ist jedoch auch möglich (für Nicht-Python 2.7-Benutzer){k:f(v) for k,v in iter(d.items())}
ytpillai
1
Spart Platz, indem ein Iterator erstellt wird
ytpillai
13

Aufgrund von PEP-0469, das iteritems () in items () umbenannte, und PEP-3113, das das Entpacken von Tuple-Parametern entfernte , sollten Sie in Python 3.x Martijn Pieters ♦ Antwort wie folgt schreiben :

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))
Lucidyan
quelle
4

Während meine ursprüngliche Antwort den Punkt verfehlt hat (indem ich versucht habe, dieses Problem mit der Lösung für den Zugriff auf den Schlüssel in der Factory of DefaultDict zu lösen ), habe ich sie überarbeitet, um eine tatsächliche Lösung für die vorliegende Frage vorzuschlagen.

Hier ist es:

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

Verwendung:

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

Die Idee ist, das ursprüngliche Diktat in Unterklassen zu unterteilen, um ihm die gewünschte Funktionalität zu geben: eine Funktion über alle Werte "abzubilden".

Der Pluspunkt ist, dass dieses Wörterbuch verwendet werden kann, um die Originaldaten so zu speichern, als ob es eine wäre dict, während alle Daten auf Anfrage mit einem Rückruf transformiert werden.

Natürlich können Sie die Klasse und die Funktion beliebig benennen (der in dieser Antwort gewählte Name ist von der PHP- array_walk()Funktion inspiriert ).

Hinweis: Weder der try- exceptBlock noch die returnAnweisungen sind für die Funktionalität obligatorisch, sie dienen dazu, das Verhalten der PHPs weiter nachzuahmen array_walk.

7heo.tk.
quelle
1
Dies löst die OP-Frage nicht, da die __missing__Methode nicht für vorhandene Schlüssel aufgerufen wird, die wir transformieren möchten, es sei denn, die übergebene Factory-Methode verwendet das Ursprungsdiktat irgendwie als Fallback, aber da dies nicht Teil der Beispielverwendung ist, Ich halte dies für eine unbefriedigende Antwort auf das vorliegende Problem.
Kaos
Welche vorhandenen Schlüssel?
7heo.tk
Aus dem OP : Given a dictionary { k1: v1, k2: v2 ... } .... Das heißt, Sie haben bereits eine dictzu Beginn ..
Kaos
Ich möchte sagen, dass wir beide Recht haben; aber ich glaube, dass wir beide falsch liegen. Sie haben Recht damit, dass meine Antwort die Frage nicht beantwortet. aber nicht aus dem Grund, den Sie angerufen haben. Ich habe einfach den Punkt verfehlt und einen Weg gefunden, um {v1: f(v1), v2: f(v2), ...}gegeben zu werden [v1, v2, ...], und kein Diktat gegeben. Ich werde meine Antwort bearbeiten, um das zu korrigieren.
7heo.tk
2

Um eine Indizierung innerhalb von Lambda zu vermeiden, gehen Sie wie folgt vor:

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

Sie können auch tun:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
mit freundlichen Grüßen
quelle
Das ist eine clevere Manipulation innerhalb des 2-Tupels selbst im zweiten Beispiel. Es verwendet jedoch das automatische Entpacken von Tupeln innerhalb des Lambda, das in Python 3 nicht mehr unterstützt wird. Daher lambda(k,v)funktioniert es nicht. Siehe stackoverflow.com/questions/21892989/…
Jonathan Komar
0

Gerade über diesen Anwendungsfall gekommen. Ich habe die Antwort von gens implementiert und einen rekursiven Ansatz für den Umgang mit Werten hinzugefügt, die auch Diktate sind:

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)

Dies kann nützlich sein, wenn Sie mit JSON- oder YamL-Dateien arbeiten, die Zeichenfolgen in Python 2 als Bytes codieren

Oyono
quelle