Seite 1 von 1

d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 08:58
von jens
Wollte mal sehen, was schneller ist:

Code: Alles auswählen

def without_setdefault1():
    d = {}
    for x in xrange(500):
        for y in xrange(100):
            if x not in d:
                d[x] = [y]
            else:
                d[x].append(y)


def without_setdefault2():
    d = {}
    for x in xrange(500):
        for y in xrange(100):
            try:
                d[x].append(y)
            except KeyError:
                d[x] = [y]


def with_setdefault():
    d = {}
    for x in xrange(500):
        for y in xrange(100):
            d.setdefault(x, []).append(y)


if __name__ == '__main__':
    from timeit import Timer

    def timeit(func, number):
        name = func.__name__
        t = Timer("%s()" % name, "from __main__ import %s" % name)
        print "%s:" % name,
        print "%.3fsec" % t.timeit(number)

    number = 200
    timeit(with_setdefault, number)
    timeit(without_setdefault1, number)
    timeit(without_setdefault2, number)
Ergebnis:

Code: Alles auswählen

with_setdefault: 3.404sec
without_setdefault1: 2.462sec
without_setdefault2: 2.361sec
Ich denke ob nun without_setdefault1 oder without_setdefault2 schneller ist, kommt darauf an wie oft x schon in d vorhanden ist oder nicht.

Welche Variante nutzt ihr am meisten? Bisher nehme ich immer without_setdefault1

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 09:14
von EyDu
Ich würde keine der drei Varianten nutzen, collections.defaultdict existiert.

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 09:50
von jens
Ah! Guter Einwand:

Code: Alles auswählen

import collections

def without_setdefault1():
    d = {}
    for x in xrange(500):
        for y in xrange(100):
            if x not in d:
                d[x] = [y]
            else:
                d[x].append(y)


def without_setdefault2():
    d = {}
    for x in xrange(500):
        for y in xrange(100):
            try:
                d[x].append(y)
            except KeyError:
                d[x] = [y]


def with_setdefault():
    d = {}
    for x in xrange(500):
        for y in xrange(100):
            d.setdefault(x, []).append(y)


def defaultdict():
    d = collections.defaultdict(list)
    for x in xrange(500):
        for y in xrange(100):
            d[x].append(y)


if __name__ == '__main__':
    from timeit import Timer

    def timeit(func, number):
        name = func.__name__
        t = Timer("%s()" % name, "from __main__ import %s" % name)
        print "%s:" % name,
        print "%.3fsec" % t.timeit(number)

    number = 200
    timeit(defaultdict, number)
    timeit(with_setdefault, number)
    timeit(without_setdefault1, number)
    timeit(without_setdefault2, number)
Ist auch die schnellste Variante:

Code: Alles auswählen

defaultdict: 1.910sec
with_setdefault: 3.355sec
without_setdefault1: 2.442sec
without_setdefault2: 2.346sec

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 09:53
von Leonidas
tl;dr: Pythonischste Lösung ist die schnellste. Weitergehn, hier gibts nichts zu sehn ;)

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 10:09
von jens
und welche findest du die Pythonischste?

Ich favorisiere z.Z. collections.defaultdict()... dabei sieht die Iteration am saubersten aus.

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 11:13
von Leonidas
jens hat geschrieben:und welche findest du die Pythonischste?
Steht doch da, die schnellste, und das ist ``defaultdict``.

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 11:21
von snafu
Entgegen früherer Praxis gehe ich inzwischen vermehrt zu `try:`-Konstrukten über. Zwar ist das IMHO nicht ganz so hübsch zu schreiben, aber eine Prüfung auf Vorhandensein z.B. eines Schlüssels im Wörterbuch findet ja bei der Abfrage des Schlüssels ohnehin statt, damit ggf eine Ausnahme geworfen werden kann. Wenn man das vorher schon selbst in Python-Code vorgenommen hat, dann findet diese Überprüfung also doppelt statt. Das kostet natürlich immer ein bißchen an zusätzlicher Zeit. Da ich weiß, dass es beim `try:`-Ansatz nur dann auf die Performance geht, wenn tatsächlich eine Exception geworfen wird (was ja buchstäblich die Ausnahme bzw der weniger häufige Fall sein sollte), kann das mitunter durchaus praktikabel sein. Es sei aber natürlich angemerkt (vor allem für mitlesende Anfänger), dass solcherlei Optimierungen *eigentlich* selten sinnvoll sind. Zudem bietet Python (wie hier im Thread schon gezeigt) oft auch optimierte eigene Objekte für bestimmte Anforderungen an (hier: `collections.defaultdict`).

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 12:33
von Leonidas
Außerdem entspricht die vorherige Prüfung nicht dem EAFP-Prinzip, welches in Python LBYL bevorzugt wird.

Re: d.setdefault(x, []).append(y)

Verfasst: Dienstag 24. Juli 2012, 20:29
von DasIch
Ich nutze üblicherweise setdefault es sei den dass dict wird nur innerhalb einer Funktion/Methode benutzt und verlässt diesen Raum nicht.