Zweidimensionales Dictionary mit Daten aus einem dreidimensionalen Dictionary erstellen

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.
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Danke für die gut verständlichen Erklärungen.
__blackjack__ hat geschrieben: Samstag 9. November 2019, 21:39 @Atalanttore: Es ist eigentlich egal wie lang eine Bedingung ist: Klammern drum und ein ``not`` davor funktioniert *immer* für eine Negation. Also Ausgangeslage:

Code: Alles auswählen

    if (
        exclude is not None
        and exclude.get(vehicle_type)
        and model in exclude.get(vehicle_type)
    ):
        ...
Zum negieren einfach ein ``not`` davor:

Code: Alles auswählen

    if not (
        exclude is not None
        and exclude.get(vehicle_type)
        and model in exclude.get(vehicle_type)
    ):
        ...
Wenn man das ``not`` da jetzt rein ziehen will, geht das nach einem festen und eigentlich relativ einfachen Schema: Alle Teilbedingungen negieren und ``and``/``or`` austauschen:

Code: Alles auswählen

    if (
        exclude is None
        or not exclude.get(vehicle_type)
        or model not in exclude.get(vehicle_type)
    ):
        ...
Ich habe zwar alle Bedingungen negiert, aber `and` habe ich nicht durch `or` ersetzt. Das war dann wohl das Problem.

__blackjack__ hat geschrieben: Samstag 9. November 2019, 21:39 Ich habe da eine Liste gewählt weil es im Argument wenn es vorhanden ist eine Liste sein sollte. Oder ein `set`. Aber kein Tupel. Funktionieren tun alle drei. Tupel halt nicht weil man als Leser bei Tupeln erwartet, dass die Bedeutung der Elemente von der Position abhängt.
Hängt bei einer Liste die Bedeutung der Elemente nicht auch von ihrer Position ab?

Bisher habe ich die Eigenschaften der verschiedenen Datenstrukturen so verstanden:

Liste: Änderbar, Elemente geordnet in Position, Elemente können mehrfach vorkommen
Tupel: Nicht änderbar, Elemente geordnet in Position, Elemente können mehrfach vorkommen
Set: Änderbar, Elemente an beliebiger Position, Elemente können nicht mehrfach vorkommen

Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Bei einer Liste sollte die Bedeutung eines Elements nicht von der Position abhängen. Alle Elemente sollten dort die gleiche Bedeutung haben. Anders beim Tupel. Wenn dort jedes Element die gleiche Bedeutung hat, ist das ein Zeichen das man eigentlich eine Liste verwenden wollte. Ausnahmen sind Fälle wo man ein Tupel wegen dessen Eigenschaften braucht, weil man es beispielsweise als Schlüssel in einem Wörterbuch verwenden will, oder in einem `set` speichern.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Danke für die Erklärung.

Gruß
Atalanttore
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Der Funktion `create_mileage()` habe ich mit freundlicher Unterstützung meiner IDE PyCharm nun auch einen docstring verpasst.

Code:

Code: Alles auswählen

def create_mileage(exclude=None):
    """
    Create a new mileage dictionary with default “unknown” value.

    :param exclude: Type and model of vehicles to be excluded from the dictionary.
    :return: dict[str, dict[str, set[str]]]
    """
    if exclude is None:
        exclude = dict()
    result = dict()
    for vehicle_type, vehicle_models in VEHICLES.items():
        inner = dict()
        for model in vehicle_models:
            if model not in exclude.get(vehicle_type, {}):
                inner[model] = "unknown"
        if inner:
            result[vehicle_type] = inner
    return result
Zur Beschreibung des Rückgabewertes habe ich einfach die jeweiligen Datentypen von außen nach innen angegeben. Macht man das so?

Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Nein bei ``:return:`` beschreibt man als Text was da zurückgegeben wird. Der Rückgabe*typ* ist ``:rtype:``. Wobei Du da anscheinend auch gar nicht die Rückgabe beschreibst, sondern den Typ von `exclude`-Argument.

Wobei ich denke dass so eine Typbeschreibung nicht wirklich sinnvoll ist. Das ist zu kompliziert, denn der Benutzer will ja nicht unbedingt oder nicht unbedingt nur wissen wie der verschachtelte Typ aussieht, sondern was die einzelnen Werte *bedeuten*. Das geht bei so verschachtelten Grunddatentypen IMHO am besten über Beispiele.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Wäre folgender docstring besser?

Code: Alles auswählen

"""
Create a new mileage dictionary with default “unknown” value.

:param exclude: Type and model of vehicles to be excluded from the dictionary.
:return: {“type of the vehicle“: {“model of the vehicle“: “unknown“}}
"""
Gruß
Atalanttore
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ist es nicht besser, wenn "excludes" eine Liste ist? Dann könnte man mit Sets arbeiten:

Code: Alles auswählen

def make_mileage(vehicles, excludes=None):
    if excludes is None:
        excludes = {}
    mileage = {}
    for kind, models in vehicles.items():
        if kind in excludes:
            models = set(models) - set(excludes[kind])
        if models:
            mileage[kind] = dict.fromkeys(models, "unknown")
    return mileage

def main():
    vehicles = {"car":   {"VW Polo":              {"power": 75,  "color": "blue"},
                          "BMW 5 series":         {"power": 400, "color": "black"}},
                "truck": {"MAN TGL":              {"power": 200, "color": "red"},
                          "Mercedes-Benz Actros": {"power": 500, "color": "white"}}
               }
    excludes = {"car": ["VW Polo"], "truck": ["Mercedes-Benz Actros"]}
    print(make_mileage(vehicles, excludes))

if __name__ == '__main__':
    main()
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nochmal etwas umgeformt und mit der neuen Python 3.8 Syntax:

Code: Alles auswählen

def make_mileage(vehicles, excludes=None):
    if excludes is None:
        excludes = {}
    mileage = {}
    for kind, models in vehicles.items():
        if models := models.keys() - excludes.get(kind, []):
            mileage[kind] = dict.fromkeys(models, "unknown")
    return mileage
Lässt sich auch als Dict Comprehension ausdrücken, aber ich finde das dann nicht mehr lesbar. Selbst jetzt ist es ja schon grenzwertig, weil da relativ viel auf kleinstem Raum passiert.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Wieder etwas gelernt: ein dictkey-Objekt verhält sich so, wie man es sich von einem Set wünschen würde:

Code: Alles auswählen

>>> {7} - [7]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'set' and 'list'
>>> {7:4}.keys() - [7]
set()
Was noch verwirrender ist, auch die umgedrehten Operanden funktionieren:

Code: Alles auswählen

>>> [7] - {7:4}.keys()
set()
Die Operatoren sind alle in erweiterter Form implementiert, die Methoden fehlen aber bis auf isdisjoint.
Wer hat sich da welche Gedanken gemacht?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Och ja, tupleobject.c und listobject.c sind sich auch sehr ähnlich und trotzdem gibt es keine gemeinsame Quelldatei als Basis. Das muss man nicht verstehen und auch nicht, warum die Schlüssel teilweise mächtiger als ein Set sind... 🤷🏿‍♂️
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Dokumentation ist in dieser Beziehung aber auch recht sparsam:
For set-like views, all of the operations defined for the abstract base class collections.abc.Set are available (for example, ==, <, or ^).
An anderer Stelle für Sets:
difference(*others)
set - other - ...
Return a new set with elements in the set that are not in the others.
werden zwar `differenz` und ´-` in einem Satz genannt, ersteres funktioniert aber mit irgendeinem Iterable, zweiteres nur mit Set-Like.
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@Sirius3 + snafu: Danke für die alternativen Lösungsvorschläge und den Code.

Gruß
Atalanttore
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

__blackjack__ hat geschrieben: Samstag 9. November 2019, 21:39

Code: Alles auswählen

    if (
        exclude is None
        or not exclude.get(vehicle_type)
        or model not in exclude.get(vehicle_type)
    ):
        ...
Ja, das mit dem `None` ist ein Idiom in Python. Das Problem bei veränderbaren Objekten als Defaultwerten ist, dass die nur ein einziges mal erstellt werden, nämlich wenn die ``def``-Anweisung ausgeführt wird um die Funktion oder Methode zu definieren. Wenn man da nicht aufpasst, handelt man sich leicht einen verstecketen, globalen Wert ein. Dummes Beispiel:

Code: Alles auswählen

In [8]: def f(xs=[]): 
   ...:     xs.append(42) 
   ...:     print(xs) 
   ...:                                                                         

In [9]: f()                                                                     
[42]

In [10]: f()                                                                    
[42, 42]

In [11]: f()                                                                    
[42, 42, 42]

In [12]: f()                                                                    
[42, 42, 42, 42]
Das wäre im Fall von `create_mileage()` kein Problem weil auf das Wörterbuch nur lesend zugegriffen wird, aber nur um niemanden zu verwirren, habe ich da trotzdem `None` genommen. Es gibt nämlich auch Fälle wo man das absichtlich machen kann. Beispielsweise um einen Cache für Funktionsergebnisse anzulegen. Ist natürlich ein Hack und eine Lösung mit einem Dekorator ist schöner.
In welchen Situationen macht dieses verwirrende Verhalten von Python in der Praxis einen Sinn?

Gruß
Atalanttore
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Keine Zeit mit der Erstellung von Objekten zu verschwenden wenn es eigentlich nicht nötig ist. Wenn es anders wäre, hätte jede Funktion eine Zeile mehr, mit letztlich unbekannten Laufzeit-Folgen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Man kann natürlich auch bei meinem Beispiel einfach ein leeres Wörterbuch in der Signatur für "excludes" nehmen. Dann spart man sich anfangs den Test samt Ersetzung und somit zwei Zeilen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier nun doch mal als Dict Comprehension:

Code: Alles auswählen

def make_mileages(vehicles, excludes={}):
    return {
        kind: dict.fromkeys(remaining, "unknown")
        for kind, models in vehicles.items()
        if (remaining := models.keys() - excludes.get(kind, []))
    }
Antworten