it-swarm.com.de

Warum gibt ein python dict.update () das Objekt nicht zurück?

Ich versuche zu tun:

award_dict = {
    "url" : "http://facebook.com",
    "imageurl" : "http://farm4.static.flickr.com/3431/3939267074_feb9eb19b1_o.png",
    "count" : 1,
}

def award(name, count, points, desc_string, my_size, parent) :
    if my_size > count :
        a = {
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }
        a.update(award_dict)
        return self.add_award(a, siteAlias, alias).award

Aber wenn ich mich in der Funktion wirklich umständlich gefühlt hätte und es mir lieber gemacht hätte:

        return self.add_award({
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }.update(award_dict), siteAlias, alias).award

Warum gibt update das Objekt nicht zurück, damit Sie es verketten können?

JQuery führt dies aus, um Verkettungen durchzuführen. Warum ist es in Python nicht akzeptabel?

115
Paul Tarjan

Python implementiert meistens eine pragmatisch angehauchte Variante von Trennung von Befehlen und Abfragen : Mutatoren geben None zurück (mit pragmatisch induzierten Ausnahmen wie pop ;-), also können sie möglicherweise nicht mit Accessoren verwechselt werden (und in der gleichen Weise ist die Zuweisung kein Ausdruck, die Trennung von Anweisung und Ausdruck ist da und so weiter).

Das bedeutet nicht, dass es nicht viele Möglichkeiten gibt, Dinge zusammenzuführen, wenn Sie es wirklich wollen, z. B. macht dict(a, **award_dict) ein neues Diktat, ähnlich dem, das Sie gerne zurückgegeben hätten .update - Warum also nicht DAS verwenden, wenn Sie es wirklich für wichtig halten?

Bearbeiten : Übrigens müssen Sie in Ihrem speziellen Fall unterwegs auch nicht a erstellen:

dict(name=name, description=desc % count, points=points, parent_award=parent,
     **award_dict)

erstellt ein einzelnes Diktat mit genau der gleichen Semantik wie Ihr a.update(award_dict) (einschließlich der Tatsache, dass Einträge in award_dict die explizit von Ihnen angegebenen überschreiben, um die andere Semantik zu erhalten) Um explizite Einträge zu haben, die solche Konflikte "gewinnen", übergeben Sie award_dict als einziges positional arg, before die Schlüsselwörter ones und Ohne das Formular ** - dict(award_dict, name=name etc etc).

188
Alex Martelli

Die API von Python unterscheidet gemäß Konvention zwischen Prozeduren und Funktionen. Funktionen berechnen neue Werte aus ihren Parametern (einschließlich aller Zielobjekte). Prozeduren modifizieren Objekte und geben nichts zurück (d. h. sie geben keine zurück). Verfahren haben also Nebenwirkungen, Funktionen nicht. update ist eine Prozedur und gibt daher keinen Wert zurück.

Die Motivation dafür ist, dass Sie ansonsten möglicherweise unerwünschte Nebenwirkungen bekommen. Erwägen

bar = foo.reverse()

Wenn reverse (das die Liste an Ort und Stelle umkehrt) auch die Liste zurückgeben würde, könnten Benutzer denken, dass reverse eine neue Liste zurückgibt, die bar zugewiesen wird, und niemals bemerken, dass foo auch modifiziert wird. Wenn Sie die Umkehrung auf "Keine" setzen, erkennen Sie sofort, dass der Balken nicht das Ergebnis der Umkehrung ist, und sehen genauer, wie sich die Umkehrung auswirkt.

33
>>> dict_merge = lambda a,b: a.update(b) or a
>>> dict_merge({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}

Beachten Sie, dass das zusammengeführte Diktat nicht nur zurückgegeben, sondern auch der erste Parameter geändert wird. Dict_merge (a, b) ändert also a.

Oder natürlich können Sie alles inline erledigen:

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
13

Das ist ganz einfach:

(lambda d: d.update(dict2) or d)(d1)
8

nicht genug Ruf für Kommentar in der obersten Antwort

@beardc das scheint keine CPython-Sache zu sein. PyPy gibt mir "TypeError: Schlüsselwörter müssen Strings sein"

Die Lösung mit **kwargs funktioniert nur, weil das zusammenzuführende Wörterbuch nur Schlüssel vom Typ string enthält.

d.h.

>>> dict({1:2}, **{3:4})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

vs

>>> dict({1:2}, **{'3':4})
{1: 2, '3': 4}
8

Es ist nicht so, dass es nicht akzeptabel ist, sondern dass dicts nicht auf diese Weise implementiert wurden.

Wenn Sie sich Djangos ORM ansehen, wird die Verkettung in großem Umfang genutzt. Es wird nicht entmutigt, Sie könnten sogar von dict erben und nur update überschreiben, um ein Update durchzuführen und return self, wenn du es wirklich willst.

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self
5
Esteban Küber

so nah wie möglich an Ihrer vorgeschlagenen Lösung

from collections import ChainMap

return self.add_award(ChainMap(award_dict, {
    "name" : name,
    "description" : desc_string % count,
    "points" : points,
    "parent_award" : parent,
}), siteAlias, alias).award
2
Matus

Für diejenigen, die zu spät zur Party kommen, hatte ich ein Timing zusammengestellt (Py 3.7), um zu zeigen, dass .update()-basierte Methoden ein bisschen (~ 5%) schneller aussehen, wenn Eingaben erhalten bleiben und spürbar (~ 30%) schneller, wenn nur ein Update durchgeführt wird.

Wie üblich sollten alle Benchmarks mit einem Körnchen Salz genommen werden.

def join2(dict1, dict2, inplace=False):
    result = dict1 if inplace else dict1.copy()
    result.update(dict2)
    return result


def join(*items):
    iter_items = iter(items)
    result = next(iter_items).copy()
    for item in iter_items:
        result.update(item)
    return result


def update_or(dict1, dict2):
    return dict1.update(dict2) or dict1


d1 = {i: str(i) for i in range(1000000)}
d2 = {str(i): i for i in range(1000000)}

%timeit join2(d1, d2)
# 258 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit join(d1, d2)
# 262 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dict(d1, **d2)
# 267 ms ± 2.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit {**d1, **d2}
# 267 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Die Zeitabläufe für die Vor-Ort-Vorgänge sind etwas kniffliger, sodass sie während eines zusätzlichen Kopiervorgangs geändert werden müssen (der erste Zeitablauf dient nur als Referenz):

%timeit dd = d1.copy()
# 44.9 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit dd = d1.copy(); join2(dd, d2)
# 296 ms ± 2.05 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dd = d1.copy(); join2(dd, d2, True)
# 234 ms ± 1.02 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dd = d1.copy(); update_or(dd, d2)
# 235 ms ± 1.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
0
norok2

Ich habe es nur selbst in Python 3.4 versucht (daher konnte ich die schicke {**dict_1, **dict_2} - Syntax nicht verwenden).

Ich wollte in der Lage sein, nicht-String-Schlüssel in Wörterbüchern zu haben sowie eine beliebige Anzahl von Wörterbüchern bereitzustellen.

Außerdem wollte ich ein neues Wörterbuch erstellen, also habe ich mich dafür entschieden, collections.ChainMap Nicht zu verwenden (irgendwie der Grund, warum ich dict.update Ursprünglich nicht verwenden wollte).

Folgendes habe ich letztendlich geschrieben:

def merge_dicts(*dicts):
    all_keys  = set(k for d in dicts for k in d.keys())
    chain_map = ChainMap(*reversed(dicts))
    return {k: chain_map[k] for k in all_keys}

merge_maps({'1': 1}, {'2': 2, '3': 3}, {'1': 4, '3': 5})
# {'1': 4, '3': 5, '2': 2}
0
freebie
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))
0
Matt