Frage zu Umgang mit get()

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Hallo Zusammen,

ich habe mal eine Frage zur dem Ermitteln von Einträgen aus Dictionaries.
Wie würdet ihr diese am Besten ermitteln, bzw. welchen Code würdet ihr
bevorzugen ?

Code: Alles auswählen

import re
from contextlib import suppress

examples = {"Parameters": {"Eins": "One", "Zwei": "Two", "Drei": "Three"}}

# Example I 

print("Eins :", examples["Parameters"][ "Eins" ] )

from contextlib import suppress

# Example II

with suppress(KeyError):
    print("Zwei :", examples["Parameters"].get("Zwei"))

# Example III

try:
    print("Drei :", examples["Parameters"].get("Drei"))
except KeyError:
    print("The key is not existing !")
Tor 1 , 2 oder 3 ? :)

VG
YAPD
-----
Yet Another Python Developer
Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

Nr.1, get verwende ich nur wenn auch Default value vorkommen kann.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
nezzcarth
User
Beiträge: 1764
Registriert: Samstag 16. April 2011, 12:47

Den 'suppress'-Kontextmanager habe ich glaube ich selbst noch nie verwendet. Das Gute an 'get' ist, dass es einen zweiten Parameter erhalten kann, der zurückgegeben wird, wenn der Key nicht enthalten ist. Das ist standardmäßig 'None', kann aber zum Beispiel auch eine leere Liste sein. Das bietet sich also oft in den Fällen an, in denen man den Wert bei Nicht-Existenz auf irgendeinen Default setzen möchte, um damit weiterarbeiten zu können. Die Variante mit direktem Dictionary-Zugriff (also ohne get) und anschließender Exception ist in manchen Situationen eine Alternative zu 'get', und bietet sich ansonsten in Fällen wie dem Gezeigten an, in denen am z.B. einfach nur eine Fehlermeldung ausgeben möchte.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@YAPD,

[Edit] Ich hatte mich wohl verlesen.
Beispiel 1: Ja, genau so
Wenn ich im Normalfall keinen KeyError erwarte, würde ich den überhaupt nicht behandeln.
Wenn dann aber doch mal ein Fehler auftritt soll er mir auch gleich krachend um die Ohren fliegen.
Exceptionhandling ist ja dazu gedacht, dass man einen solchen Fehlerfall durchaus für möglich hält und dafür auch eine sinnvolle Alternative hat.

Beispiel 2: Nein, weil ich keine Notwendigkeit für suppress sehe
Beispiel 3: Nein, entweder get() mit default value, oder wenn es ein komplexerer Fall ist, ohne get() aber mit KeyError handling
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

nezzcarth hat geschrieben: Montag 9. August 2021, 11:56 Den 'suppress'-Kontextmanager habe ich glaube ich selbst noch nie verwendet. Das Gute an 'get' ist, dass es einen zweiten Parameter erhalten kann, der zurückgegeben wird, wenn der Key nicht enthalten ist. Das ist standardmäßig 'None', kann aber zum Beispiel auch eine leere Liste sein. Das bietet sich also oft in den Fällen an, in denen man den Wert bei Nicht-Existenz auf irgendeinen Default setzen möchte, um damit weiterarbeiten zu können. Die Variante mit direktem Dictionary-Zugriff (also ohne get) und anschließender Exception ist in manchen Situationen eine Alternative zu 'get', und bietet sich ansonsten in Fällen wie dem Gezeigten an, in denen am z.B. einfach nur eine Fehlermeldung ausgeben möchte.
Danke für deine Antwort. Das stimmt allerdings, klappt aber nur mit eindimensionalen Dics. Sobald der erste der beiden Keys nicht existiert, wird
eine KeyError Meldung ausgelöst. Deshalb die Möglichkeit mit suppress. Allerdings wird die Variable dann gar nicht angezeigt, ohne Fehlermeldung.
rogerb hat geschrieben: Montag 9. August 2021, 12:07 @YAPD,

[Edit] Ich hatte mich wohl verlesen.
Beispiel 1: Ja, genau so
Wenn ich im Normalfall keinen KeyError erwarte, würde ich den überhaupt nicht behandeln.
Wenn dann aber doch mal ein Fehler auftritt soll er mir auch gleich krachend um die Ohren fliegen.
Exceptionhandling ist ja dazu gedacht, dass man einen solchen Fehlerfall durchaus für möglich hält und dafür auch eine sinnvolle Alternative hat.

Beispiel 2: Nein, weil ich keine Notwendigkeit für suppress sehe
Beispiel 3: Nein, entweder get() mit default value, oder wenn es ein komplexerer Fall ist, ohne get() aber mit KeyError handling
Danke für deine Antwort. Ich denke, auch dass die 1. ( Standard ) Lösung am Besten ist, es gibt
aber, denke ich, schon Situation, in denen man die try bzw. suppress() Lösung verwenden kann.

VG
YAPD
-----
Yet Another Python Developer
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

"try/except" und "get" sind sinnvoll, wenn es vorkommen kann, dass ein Key nicht existiert. Ein bisschen ist es ein Stilfrage, welche Form bevorzugt werden soll. Falls es aber eine Laufzeitfrage sein sollte, dann ist es gut zu wissen, dass ein "try" preiswerter ist als ein "get", ein "except" aber deutlich teurer. D.h. "try/except" ist von Vorteil, wenn wenig KeyErrors erwartet werden, ansonsten kann "get" zu bevorzugen sein. Um den Unterschied zu bemerken, bedarf es aber einer starken Frequentierung. Für "suppress" habe ich noch keine Anwendung gefunden. Im Gegenteil – ich möchte gar keine Exceptions unterdrücken.
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

kbr hat geschrieben: Montag 9. August 2021, 14:24 "try/except" und "get" sind sinnvoll, wenn es vorkommen kann, dass ein Key nicht existiert. Ein bisschen ist es ein Stilfrage, welche Form bevorzugt werden soll. Falls es aber eine Laufzeitfrage sein sollte, dann ist es gut zu wissen, dass ein "try" preiswerter ist als ein "get", ein "except" aber deutlich teurer. D.h. "try/except" ist von Vorteil, wenn wenig KeyErrors erwartet werden, ansonsten kann "get" zu bevorzugen sein. Um den Unterschied zu bemerken, bedarf es aber einer starken Frequentierung. Für "suppress" habe ich noch keine Anwendung gefunden. Im Gegenteil – ich möchte gar keine Exceptions unterdrücken.
Hi kbr,

vielen Dank für deine Antwort. Eigentlich haben mich die Unterschiede interessiert, aber ist wirklich gut zu wissen, denn
den Code möglichst performant zu schreiben, ist ja einer der wichtigsten Dinge beim Programmieren. Aber noch eine
Frage. Wieso sollte man einen try ohne except machen ? D.h. also eigentlich sind gets( ) immer guenstiger als trys /
excepts ?

VG
YAPD
-----
Yet Another Python Developer
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

@YAPD: es gibt kein try ohne except. Der except branch wird aber nur ausgeführt, wenn ein KeyError auftritt. Wenn keiner auftritt, ist get teurer. Das wirst Du in der Praxis aber erst merken, wenn innerhalb kurzer Zeit extrem viele Zugriffe auf ein Dictionary erfolgen. Es zwar gut, performanten Code zu schreiben, aber noch wichtiger ist zu wissen, wann das wirklich erforderlich ist. Erster Ansatzpunkt ist hier in der Regel der Algorithmus und nicht Details wie try und get.
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

kbr hat geschrieben: Montag 9. August 2021, 15:14 @YAPD: es gibt kein try ohne except. Der except branch wird aber nur ausgeführt, wenn ein KeyError auftritt. Wenn keiner auftritt, ist get teurer. Das wirst Du in der Praxis aber erst merken, wenn innerhalb kurzer Zeit extrem viele Zugriffe auf ein Dictionary erfolgen. Es zwar gut, performanten Code zu schreiben, aber noch wichtiger ist zu wissen, wann das wirklich erforderlich ist. Erster Ansatzpunkt ist hier in der Regel der Algorithmus und nicht Details wie try und get.
Wo wir gerade bei Performance sind, kannst du mir erklären warum ich bei Vergleich der Geschwindigkeit
folgender ( identischen ) Codes bei beiden Werten die gleiche Zeit erhalte :

Code: Alles auswählen

from collections import Counter
from collections import defaultdict
import time
from datetime import datetime

portfolio = [
    ('GOOG', 100, 490.1),
    ('IBM', 50, 91.1),
    ('CAT', 150, 83.44),
    ('IBM', 100, 45.23),
    ('GOOG', 75, 572.45),
    ('AA', 50, 23.15)
]

total_shares = Counter()
Shares = {}

for name, shares, price in portfolio:
    total_shares[name] += shares

startTime = datetime.now()


def V1():

    for rowno, row in enumerate(portfolio, start=0):

        if row[0] in Shares:
            Shares[row[0]].append((row[1], row[2]))
        else:
            Shares[row[0]] = [(row[1], row[2])]

    cpu_time = time.process_time()
    print("CPU / Processing Time : ", cpu_time)

    print(datetime.now() - startTime)


def V2():

    holdings = defaultdict(list)

    for name, shares, price in portfolio:
        holdings[name].append((shares, price))

    cpu_time= time.process_time()
    print("CPU / Processing Time : ", cpu_time)

    print(datetime.now() - startTime)
Der Code in V1 macht exakt das selbe, nur ohne "defaultdict( )", natürlich weiß ich,
dass es besser "defaultdict( )" zu verwenden. Daher auch meine Frage. Warum sind
beide Codes gleich schnell, oder mache ich was falsch ?

Code: Alles auswählen

V1 : CPU / Processing Time :  0.046875
V1 : 0:00:00

V2 : CPU / Processing Time :  0.046875
V2 : 0:00:00
VG
YAPD
-----
Yet Another Python Developer
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Deine Zeitmessung ist sehr ungenau, von daher sind die minimalen Unterschiede nicht sichtbar.
Du misst hauptsächlich die Startup-Zeit.

@kbr: einem try-Block kann auch ein finally-Block folgen, ohne except-Block.
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sirius3: ja sicher. Oder mehrere excepts und auch noch ein else. Ein try/finally ist mit einem get aber nicht mehr vergleichbar, da die Exception weitergereicht wird.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

@YAPD: Deine Variante 1 ist ein gutes Beispiel für schlecht lesbaren Code. Bevor du anfängst über Geschwindigkeitsoptiomierungen nachzudenken, solltest du verständlichen Code zu schreiben. Und falls du irgendwann mal ein Geschwindigkeitsproblem hast, dann kannst du die Optimierung angehen.

Variablennamen schreibt man klein_mit_unterstrich. Klassennamen (nicht die Namen der Instanzen) schreibt man PascalCase.
Du verwendest bei Variante 1 enumerate, brauchst aber den Index gar nicht. Außerdem verwendest du da "magische Index-Zugriffe", die man erst mühsam nachvollziehen muss. In Variante 2 machst du das besser, da bindest du die Elemente an Namen.
Vernünftige Namen sind übrigens meist sehr hilfreich.
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@YAPD: Für solche Messungen sollte man das `timeit`-Modul verwenden. Das umschifft diverse Probleme die auftreten können, damit meinen keinen, oder weniger Mist misst. 🙂
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Hi Zusammen,

vielen Dank für alle Eure Antworten. Dass ich den o.g. Code gepostet habe, hat den Hintergrund, dass ich gerade noch
am Lernen bin und mir defaultdict angeschaut habe. Ich habe dann zu Übungszwecken versucht, den Code abzubilden,
nur um zu verstehen, was es macht. ( Mich interessiert immer auch der Hintergrund ). Ich weiß, dass man Variablen
immer klein schreibt, bei "Counter" und "Shares" hab ich es übersehen, Flüchtigkeitsfehler :)

VG
YAPD
-----
Yet Another Python Developer
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Ohne mit die Implementierung von defaultdict angeschaut zu haben, würde ich mal stark davon ausgehen dass die auf __missing__ aufbaut. Dementsprechend wird weder KeyError noch .get() involviert sein.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@DasIch,
Dementsprechend wird weder KeyError noch .get() involviert sein.
Doch, DefaultDict implementiert __missing__ () was bei einem fehlenden Key einen KeyError wirft wenn keine factory Function angegeben wurde.
get() gibt es auch und verhält sich so wie bei einem Standard-Dictionary.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

@rogerb: "involviert" ist nicht "implementiert"
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@sparrow,
"involviert" ist nicht "implementiert"
Und was willst du damit sagen? Was soll "involviert" eigentlich bedeuten?

Der zentrale Punkt ist, dass es auch bei defaultdict eine KeyError Exception geben kann und das man die get() Methode auch mit einem defaultdict verwenden kann.
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@rogerb: Aber wann gibt es denn einen `KeyError`? Doch nur wenn man ein `defaultdict` falsch benutzt, weil man es ohne Factory-Funktion erstellt hat, und damit eigentlich gar kein `defaultdict` bräuchte, sondern einfach ein normales Wörterbuch hätte nehmen können/sollen.

Klar gibt es auch eine `get()`-Methode, weil ein `defaultdict` auch überall da funktionieren sollte, wo ein `dict` erwartet wird, und da kann dann natürlich auch ein `get()`-Aufruf stehen. Aber in eigenem Code bzw. Code der *weiss* das es sich speziell um ein `defaultdict` handelt, macht `get()` keinen Sinn. Oder nur, wenn man vorher einen sinnvollen Grund gefunden hat `defaultdict` ohne Fabrikfunktion aufzurufen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@__blackjack__,
du hast jetzt nochmal ausführlich wiederholt, was ich schon gesagt hatte. - Danke, aber ... ?
Antworten