Wörterbuch nach Listenelementen ordnen

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.
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

Hallo, wie kann man ein Wörterbuch, in dem Listen eingetragen sind, nach Listenelementen ordnen?

Beispiel:

Code: Alles auswählen

worterbuch = {}
worterbuch["qwertz"] = [4,83,56]
worterbuch["asdfgh"] = [14,23,36]
worterbuch["yxcvbn"] = [101,44,28]
Nun möchte ich dieses Wörterbuch z. B. nach dem zweiten Listenelement ordnen.

Wenn ich ...

Code: Alles auswählen

for key in worterbuch:
    print key, worterbuch[key][1]
... schreibe, soll ...

Code: Alles auswählen

qwertz 83
yxcvbn 44
asdfgh 23
... ausgegeben werden.

Wie macht man das?

Ändert sich etwas, wenn für jedes Schlüsselwort mehrere Listen eingetragen sind?

Code: Alles auswählen

worterbuch = {}
worterbuch["qwertz"].liste1 = [4,83,56]
worterbuch["asdfgh"].liste1 = [14,23,36]
worterbuch["yxcvbn"].liste1 = [101,44,28]

Code: Alles auswählen

for key in worterbuch:
    print key, worterbuch[key].liste1[1]
BlackJack

@Aries: Ein Wörterbuch ist ungeordnet. Du müsstest also eine sortierte Liste mit den gewünschten Elementen erstellen. Listen haben eine `sort()`-Methode und aus beliebigen iterierbaren Objekten kann man mit der `sorted()`-Funktion eine sortierte Liste erstellen. Beide kennen ein `key`-Argument bei dem man eine Funktion übergeben kann, die für jedes Element einen Wert berechnet, nach dem die Elemente sortiert werden. Und da macht es natürlich einen Unterschied wo in der Datenstruktur dieser Sortierschlüssel steht, denn die Funktion muss den dort ja heraus holen.

Edit:

Code: Alles auswählen

from operator import itemgetter

get_second = itemgetter(1)


def main():
    # 
    # 1. Beispiel
    # 
    worterbuch = {
        'qwertz': [4, 83, 56], 'asdfgh': [14, 23, 36], 'yxcvbn': [101, 44, 28],
    }

    for key, value in sorted(
        ((k, v[1]) for k, v in worterbuch.iteritems()),
        key=get_second,
        reverse=True
    ):
        print key, value
    # 
    # 2. Beispiel
    # 
    print '-' * 42

    class Spam(object):
        def __init__(self, numbers):
            self.numbers = numbers
    
    worterbuch = {
        'qwertz': Spam([4, 83, 56]),
        'asdfgh': Spam([14, 23, 36]),
        'yxcvbn': Spam([101, 44, 28]),
    }
    
    for key, value in sorted(
        ((k, v.numbers[1]) for k, v in worterbuch.iteritems()),
        key=get_second,
        reverse=True
    ):
        print key, value


if __name__ == '__main__':
    main()
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

Danke für die Antwort. Funktionieren tut es bei mir. Allerdings habe ich habe ich ein paar Fragen:

Was macht die Funktion itemgetter(1), und warum kommt irgendeine unsinnige Reihenfolge heraus, wenn man stattdessen itemgetter(0) schreibt?

Warum schreibt man nicht direkt in der sorted-Funktion "key=itemgetter(1)"? Und warum wurde die stattdessen benutzte Variable "get_second" genannt? Das hat bei mir ersteinmal Verwirrung ausgelöst.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Aries hat geschrieben:Was macht die Funktion itemgetter(1), und warum kommt irgendeine unsinnige Reihenfolge heraus, wenn man stattdessen itemgetter(0) schreibt?
Wenn du wissen möchtest was eine Funktion macht, dann schaust du am besten in der Dokumentation nach. Dort ist das Verhalten genau beschrieben. ``itemgetter(i)`` liefert eine Funktion zurück, welche als Ergebnis das i-te Element einer Sequenz liefert. Wenn du ein Tupel ``(2, 42, 32)`` hast und darauf ``itemgetter(1)`` ausführst, dann bekommst du als Ergebnis 42. Die unsinnige Reihenfolge bekommst du einfach deshalb, weil du nicht mehr das zweite Element zum sortieren verwendest, sondern das erste. Das sind in deinem Fall die Strings.
Aries hat geschrieben:Warum schreibt man nicht direkt in der sorted-Funktion "key=itemgetter(1)"? Und warum wurde die stattdessen benutzte Variable "get_second" genannt? Das hat bei mir ersteinmal Verwirrung ausgelöst.
Weil Namen mehr Aussagekraft haben. Und ``get_second`` heißt die Funktion, weil sie das zweite Element zurückliefert. Der Name beschreibt also genau was passiert ;-)
Das Leben ist wie ein Tennisball.
BlackJack

Ergänzend: An einen Namen gebunden habe ich das Ergebnis von ``itemgetter(1)`` weil es mehr als einmal verwendet wird. Netter Nebeneffekt, dachte ich zumindest, ist das durch den Namen klarer wird was passiert. :-)
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

EyDu hat geschrieben:
Aries hat geschrieben:Was macht die Funktion itemgetter(1), und warum kommt irgendeine unsinnige Reihenfolge heraus, wenn man stattdessen itemgetter(0) schreibt?
Wenn du wissen möchtest was eine Funktion macht, dann schaust du am besten in der Dokumentation nach. Dort ist das Verhalten genau beschrieben. ``itemgetter(i)`` liefert eine Funktion zurück, welche als Ergebnis das i-te Element einer Sequenz liefert. Wenn du ein Tupel ``(2, 42, 32)`` hast und darauf ``itemgetter(1)`` ausführst, dann bekommst du als Ergebnis 42. Die unsinnige Reihenfolge bekommst du einfach deshalb, weil du nicht mehr das zweite Element zum sortieren verwendest, sondern das erste. Das sind in deinem Fall die Strings.
Ok, jetzt habe ich es verstanden.
Aries hat geschrieben:Warum schreibt man nicht direkt in der sorted-Funktion "key=itemgetter(1)"? Und warum wurde die stattdessen benutzte Variable "get_second" genannt? Das hat bei mir ersteinmal Verwirrung ausgelöst.
Weil Namen mehr Aussagekraft haben. Und ``get_second`` heißt die Funktion, weil sie das zweite Element zurückliefert. Der Name beschreibt also genau was passiert ;-)
Das Problem ist zum einen, dass man Gefahr läuft das "get_second" als etwas von Python vordefiniertes interpretiert wird (ich habe tatsächlich erstmal get_first ausprobiert), und zum anderen, dass, wenn man get_second umändert ( z. B. in itemgetter(0) ) - in dieser Möglichkeit besteht ja der primäre Sinn hier überhaupt eine Variable zu verwenden - die Benennung "get_second" seinen Sinn vollkommen verliert. Also in dem Fall, in dem die Verwendung der Variable sinnvoll wäre, würde sie aufgrund des Namens verwirren.

In dem Fall würde ich einfach "key=itemgetter(1)" schreiben, denn ich sehe keine Wahrscheinlichkeit, dass man das später ändern wollen sollte (noch dazu analog zu anderen Code-Abschnitten).

"itemgetter(1)" ist auch hochgradig expliziter als "get_second".
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Aries hat geschrieben:Das Problem ist zum einen, dass man Gefahr läuft das "get_second" als etwas von Python vordefiniertes interpretiert wird (ich habe tatsächlich erstmal get_first ausprobiert),
Die Zuweisung steht doch ganz oben im Quelltext, noch expliziter geht es gar nicht. Und dass du etwas als vordefiniert interpretierst ist wohl eher dein Problem, mir ist so etwas in über 15 Jahren noch nie passiert. Wenn du Versuchst Quelltext mit deiner Erwartungshaltung zu raten, dann machst du etwas falsch
Aries hat geschrieben:und zum anderen, dass, wenn man get_second umändert ( z. B. in itemgetter(0) ) - in dieser Möglichkeit besteht ja der primäre Sinn hier überhaupt eine Variable zu verwenden - die Benennung "get_second" seinen Sinn vollkommen verliert. Also in dem Fall, in dem die Verwendung der Variable sinnvoll wäre, würde sie aufgrund des Namens verwirren.
Diese Begründung ist doch vollkommen verrückt, dann dürftest du gar nichts mehr an einen Namen binden, weil man den daran gebundenen Wert ändern könnte. ``get_second`` ist ein ganz normaler Funktionsname, als wenn du selber irgendwo eine Funktion mittels def definieren würdest.
Aries hat geschrieben:In dem Fall würde ich einfach "key=itemgetter(1)" schreiben, denn ich sehe keine Wahrscheinlichkeit, dass man das später ändern wollen sollte (noch dazu analog zu anderen Code-Abschnitten).
Wenn du es später ändern möchtest, dann ändert man es einfach. Dazu muss doch überhaupt nicht ``get_second`` angepasst werden. Und noch viel schlimmer ist dein Vorschlag überall ``itemgetter(1)`` einzusetzen, weil man ggf. die key-Funktion sonst an anderen Stellen anpassen muss. Damit verdoppelst du nur Code und machst ihn noch schwerer wartbar. Wenn du weißt, dass sich die Zugriffsfunktion später ändert, dann schreibst du dafür eine neue Funktion, welche ggf. ``get_second`` verwendet.
Aries hat geschrieben:"itemgetter(1)" ist auch hochgradig expliziter als "get_second".
In diesem Fall nimmt es sich nicht viel, aber im Allgemeinen ist ein guter Funktionsname viel aussagekräftiger als eine generische Funktion mit einem Parameter. Bei ``get_second`` weiß man sofort was passiert.
Das Leben ist wie ein Tennisball.
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

EyDu hat geschrieben:
Aries hat geschrieben:Das Problem ist zum einen, dass man Gefahr läuft das "get_second" als etwas von Python vordefiniertes interpretiert wird (ich habe tatsächlich erstmal get_first ausprobiert),
Die Zuweisung steht doch ganz oben im Quelltext, noch expliziter geht es gar nicht.
Sogesehen kann ein Programm garnicht nicht explizit sein, sofern es funktioniert.

Explizit bedeutet ausdrücklich, implizit bedeutet mitgemeint. Wenn man ausdrücklich immer itemgetter(1) schreibt, dann ist das eben ausdrücklich. Wenn man immer "get_second" schreibt und itemgetter(1) meint, dann ist das eben implizit.

Die Verwendung einer Variablen wie get_second ist umso sinnvoller, je häufiger sie verwendet wird. Je häufiger sie verwendet wird, desto weiter entfernt stehen einzelne Vorkommen der Variablen aber von der Definition. Das heißt: Man muss länger nach der Definition suchen. Umso wichtiger wird ein sinnvoller Variablenname, der, da er schon nicht explizit sein kann, wenigstens implizit sein sollte. get_secongs impliziert meines Erachtens lediglich eine vermeintliche Expliziertheit, die garnicht existiert, stattdessen könnte da nämlich auch "blub" stehen.
EyDu hat geschrieben:Und dass du etwas als vordefiniert interpretierst ist wohl eher dein Problem, mir ist so etwas in über 15 Jahren noch nie passiert. Wenn du Versuchst Quelltext mit deiner Erwartungshaltung zu raten, dann machst du etwas falsch
Ja klar, man kann auch für alle Variablen sich irgendwelche abwegigen Namen wie ax, zd, dk, uv aussuchen und alles kreuz und queer miteinander verknüpfen. Wenn man den Code 100%ig analysiert wird man schon alles verstehen, macht der Compiler/Interpreter ja schließlich auch. Aber explizit ist das dann gerade nicht. Explizit ist ein Code-Abschnitt umso mehr, je unabhängiger er von anderen Code-Abschnitten ist - wenn ihn ihm eben ausdrücklich steht, was gemacht wird, ohne dass man dazu noch andere Code-Abschnitte kennen müsste.

Ich wundere mich über diese Argumentation hier, schließlich will Python ja explizit sein. Und woanders hier wurde sogar noch die Impliziertheit des pascalschen with-Ausdrucks kritisiert.
EyDu hat geschrieben:
Aries hat geschrieben:und zum anderen, dass, wenn man get_second umändert ( z. B. in itemgetter(0) ) - in dieser Möglichkeit besteht ja der primäre Sinn hier überhaupt eine Variable zu verwenden - die Benennung "get_second" seinen Sinn vollkommen verliert. Also in dem Fall, in dem die Verwendung der Variable sinnvoll wäre, würde sie aufgrund des Namens verwirren.
Diese Begründung ist doch vollkommen verrückt, dann dürftest du gar nichts mehr an einen Namen binden, weil man den daran gebundenen Wert ändern könnte. ``get_second`` ist ein ganz normaler Funktionsname, als wenn du selber irgendwo eine Funktion mittels def definieren würdest.
Nein, man müsste den Namen lediglich in z. B. get_number_i_defined_earlier umbenennen oder kurz z. B.: "get_defnum".
EyDu hat geschrieben:
Aries hat geschrieben:In dem Fall würde ich einfach "key=itemgetter(1)" schreiben, denn ich sehe keine Wahrscheinlichkeit, dass man das später ändern wollen sollte (noch dazu analog zu anderen Code-Abschnitten).
Wenn du es später ändern möchtest, dann ändert man es einfach. Dazu muss doch überhaupt nicht ``get_second`` angepasst werden.
Ja, nur dann ist es nicht nur nicht explizit, sondern auch noch etwas falsches implizierend.
EyDu hat geschrieben:Und noch viel schlimmer ist dein Vorschlag überall ``itemgetter(1)`` einzusetzen, weil man ggf. die key-Funktion sonst an anderen Stellen anpassen muss. Damit verdoppelst du nur Code und machst ihn noch schwerer wartbar. Wenn du weißt, dass sich die Zugriffsfunktion später ändert, dann schreibst du dafür eine neue Funktion, welche ggf. ``get_second`` verwendet.
Ja, nur ich habe von diesem konkreten Fall gesprochen, und in dem ist das alles irrelevant.
BlackJack

@Aries: Wenn explizit zu *dem* Grad besser sein soll als implizit, dann dürfte man gar keine Funktionen verwenden sondern müsste immer überall den Code aus dem Funktionskörper hinschreiben. Denn ``itemgetter(1)`` wäre dann auch nicht explizit sondern impliziert das was in dem Funktionskörper von der `itemgetter()`-Funktion steht. Man hätte auch eine anonyme Funktion an der Stelle schreiben können, aber ich finde es eleganter die dafür vorgesehenen Funktionen höherer Ordnung zu verwenden.

Wenn der Name `get_second()` vom Informationsgehalt für Dich gleichwertig zu `blub()` ist, dann solltest Du die Finger vom Programmieren lassen. Oder vielleicht Englisch lernen. Funktionsnamen beschreiben per Konvention die Tätigkeit, die von der Funktion durchgesführt wird und „hole das zweite” sagt mehr als „blub” (habe ich hoffentlich richtig übersetzt ;-)). Und zumindest ich finde `get_second()` sagt auch etwas direkter was passiert als ``itemgetter(1)`` weil, Achtung, das *expliziter* sagt, dass es sich um das zweite Element handelt, während man beim ``itemgetter(1)`` immer noch den gedanklichen Umweg machen muss, dass die 1 für das zweite und nicht etwa für das erste Element steht.

Was Du mit `get_number_i_defined_earlier` und `get_defnum` sagen willst erschliesst sich mir nicht‽ Was willst Du so nennen? Und was sollen die Namen sagen? Was soll das `i` im ersten Namen bedeuten? Und was soll mir das `defined_earlier` sagen? Die Funktion holt kein beliebiges `i`-tes Element und das etwas was man verwendet früher im Programmfluss mal definiert worden sein muss, ist eine Selbstverständlichkeit. Sonst gibt es nämlich einen `NameError`. Der Name `get_defnum` ist schlecht weil es eine nicht gebräuchliche Abkürzung ist. Hier ist mir auch nicht klar was das `def` in dem Namen bedeuten soll.

Da Du bei `get_second` immer von einer Variablen sprichst und das der Sinn von Variablen ist die umdefinieren zu können: Das ist falsch. Denn in Python ist *alles* was einen Namen hat in diesem Sinne variabel, aber ganz sicher sind nicht alle Namen dazu gedacht um sie im Laufe des Programms umzudefinieren. An `get_second` ist eine *Funktion* gebunden. Und natürlich verliert ein Funktionsname seinen Sinn wenn man plötzlich eine ganz andere Funktion daran bindet als die, für die dieser Name ausgesucht wurde. Würdest Du diese komische Argumentation auch bringen wenn ich das hier geschrieben hätte:

Code: Alles auswählen

def get_second(sequence):
    return sequence[1]
Das ist nämlich vom Effekt her genau das gleiche wie ``get_second = itemgetter(1)``. Die ``def``-Anweisung ist nicht die einzige Möglichkeit Funktionen zu definieren. Es gibt auch Funktionen mit denen man Funktionen erstellen kann. Zum Beispiel im `operator`-Modul und im `functools`-Modul.

Überleg mal ob das Problem nicht bei einer gedanklichen Einteilung in statischen Kram wie Funktionen und variablen Kram liegen könnte, die in Python so nicht existiert. Dafür aber einiges aus dem Werkzeugkasten von funktionaler Programmierung, wo Funktionen auch Werte sind, die man an andere Funktionen sowohl übergeben als auch als Rückgabewerte von Funktionen bekommen kann.
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

BlackJack hat geschrieben:@Aries: Wenn explizit zu *dem* Grad besser sein soll als implizit, dann dürfte man gar keine Funktionen verwenden sondern müsste immer überall den Code aus dem Funktionskörper hinschreiben. Denn ``itemgetter(1)`` wäre dann auch nicht explizit sondern impliziert das was in dem Funktionskörper von der `itemgetter()`-Funktion steht.
Das wäre in der Tat expliziter, aber dafür niederer und komplizierter. Da geht die Einfachheit klar vor.
BlackJack hat geschrieben:Wenn der Name `get_second()` vom Informationsgehalt für Dich gleichwertig zu `blub()` ist
Wenn ich

Code: Alles auswählen

get_second = itemgetter(0)
mit

Code: Alles auswählen

blub = itemgetter(0)
vergleiche, dann ist blub tatsächlich noch besser, obwohl es sauschlecht ist, immerhin impliziert es aber nichts falsches. Wenn get_second immer nur als itemgetter(1) definiert sein soll, verstehe ich nicht, warum man nicht direkt itemgetter(1) schreibt.
BlackJack hat geschrieben:Funktionsnamen beschreiben per Konvention die Tätigkeit, die von der Funktion durchgesführt wird und „hole das zweite” sagt mehr als „blub” (habe ich hoffentlich richtig übersetzt ;-)). Und zumindest ich finde `get_second()` sagt auch etwas direkter was passiert als ``itemgetter(1)`` weil, Achtung, das *expliziter* sagt, dass es sich um das zweite Element handelt, während man beim ``itemgetter(1)`` immer noch den gedanklichen Umweg machen muss, dass die 1 für das zweite und nicht etwa für das erste Element steht.
Weiß der Kuckuck, warum es bei 0 anfängt, aber das ist eine Sache, die man wenn man Python auch nur etwas gelernt hat, jawohl sehr auswendig weiß. Und itemgetter sagt aus, das es um Items geht.
BlackJack hat geschrieben: Was Du mit `get_number_i_defined_earlier` und `get_defnum` sagen willst erschliesst sich mir nicht‽ Was willst Du so nennen? Und was sollen die Namen sagen? Was soll das `i` im ersten Namen bedeuten? Und was soll mir das `defined_earlier` sagen?
Das soll get_second ersetzen.

Code: Alles auswählen

get_number_i_defined_earlier = itemgetter(1)
get_defnum = itemgetter(1)
Deutscher: get_Nummer_die_ich_zuvor_definiert_habe (get_definierte_Nummer)
BlackJack hat geschrieben:Da Du bei `get_second` immer von einer Variablen sprichst und das der Sinn von Variablen ist die umdefinieren zu können: Das ist falsch. Denn in Python ist *alles* was einen Namen hat in diesem Sinne variabel, aber ganz sicher sind nicht alle Namen dazu gedacht um sie im Laufe des Programms umzudefinieren. An `get_second` ist eine *Funktion* gebunden. Und natürlich verliert ein Funktionsname seinen Sinn wenn man plötzlich eine ganz andere Funktion daran bindet als die, für die dieser Name ausgesucht wurde. Würdest Du diese komische Argumentation auch bringen wenn ich das hier geschrieben hätte:

Code: Alles auswählen

def get_second(sequence):
    return sequence[1]
Das ist nämlich vom Effekt her genau das gleiche wie ``get_second = itemgetter(1)``.
Ich dachte get_second wird hier verwendet, damit man falls man später itemgetter(1) z. B. in itemgetter(0) ändern möchte, dies nicht zigmal im Programmcode tun muss, sondern nur einmal. Sag nicht, dabei gehts nur um Benenungsvorlieben. Das fände ich ja total sinnlos.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Aries hat geschrieben:vergleiche, dann ist blub tatsächlich noch besser, obwohl es sauschlecht ist, immerhin impliziert es aber nichts falsches. Wenn get_second immer nur als itemgetter(1) definiert sein soll, verstehe ich nicht, warum man nicht direkt itemgetter(1) schreibt.
Natürlich ist ``get_second`` immer an ``itemgetter(1)`` gebunden, alles andere wäre doch selten dämlich. Man ändert doch keine Funktionen in der Weise, dass sie etwas vollkommen anderes machen als der Name impliziert. Wenn man statt des zweiten Elements das dritte haben will, dann erstellt man sich eine neue Funktion und passt das key-Keyword entsprechend an, bzw. verwendet für alle zusammenhängende key-Funktionen bereits eine Funktion mit abstrakten Namen:

Code: Alles auswählen

def key_function(x):
    return get_second(x)
das kann man dann ggf. ganz einfach in

Code: Alles auswählen

def key_function(x):
    return get_first(x)
ändern, ohne dass man an diversen stellen im Code Anpassungen vornehmen möchte. Damit hast du dann sogar, ganz *explizit*, ausgedrückt, welche Codeteile zusammengehören. Und das ist auch der Grund, warum überall das Einfügen von ``itemgetter(1)`` eine schlechte Idee ist. Am Ende stehst du mit 27 solcher Codestücke da und musst sie unter Umständen anpassen. Und das ganze wird noch schlimmer dadurch, dass du keine gemeinsame Funktion (wie key_function) verwendet hast, da du nicht einmal weißt welche Teile du anpassen musst. Gut, in diesem Beispiel vielleicht ein wenig akademisch, aber darauf läuft es am Ende hinaus.
Aries hat geschrieben:Weiß der Kuckuck, warum es bei 0 anfängt, aber das ist eine Sache, die man wenn man Python auch nur etwas gelernt hat, jawohl sehr auswendig weiß.
Da man sich so einige Mutliplikationen und Additionen beim Mapping auf den Speicher spart. Ist heute natürlich kaum noch von Bedeutung, aber noch immer gute alte Tradition.
Aries hat geschrieben:Ich dachte get_second wird hier verwendet, damit man falls man später itemgetter(1) z. B. in itemgetter(0) ändern möchte, dies nicht zigmal im Programmcode tun muss, sondern nur einmal. Sag nicht, dabei gehts nur um Benenungsvorlieben. Das fände ich ja total sinnlos.
Nein, wie oben schon geschrieben: ``get_second`` macht genau das was der Name sagt. Nichts anderes.
Das Leben ist wie ein Tennisball.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Um nochmal 2 deiner Kernpunkte aufzugreifen:

`itemgetter(1)` ist expliziter als `get_second`: Nein, das ist es nicht. `get_second` drueckt _ganz_ explizit aus was es tut, waehrend man `itemgetter(1)` erst einmal dekodieren muss. Du hast doch selbst nach der Bedeutung von `itemgetter(1)` gefragt. Das spricht nicht dafuer, dass es deutlich macht, was es tut.

`get_second` koennte sich aendern: Ja, aber nur durch Code, der das auch tut. Und wie BlackJack schon sagte: Das gilt fuer _alle_ anderen Namen. Ob sie durch Zuweisung, Klassen- oder Funktionsdefinitionen erzeugt wurden.

Sowas wie `get_second` einzufuehren ist hier nicht unbedingt noetig, kann aber hilfreich sein (siehe Punkt 1). Wenn man aber mehrere Codestuecke hat, die auf derselben Datenstruktur bzw demselben Key operieren, hat man schnell ein Konsistenzproblem, wenn man es nicht tut und sich etwas an der Datenstruktur aendert.

Oder man will die Sortierung aendern und statt den Listen die Laenge der Listen nehmen, oder, oder oder ...

Zusammengefasst gibt es zwei Hauptgruende fuer einen eigenen Namen:

1. Man will die _Absicht_ explizit machen (das ist vorallem mit "explicit is better than implicit" gemeint, nicht den Code explizit machen), `get_second` ist hier als Name nicht sonderlich gut geeignet, weil es synonym mit `itemgetter(1)` ist, mehr gibt die abstrakte Problemstellung aber nicht her. Weiss man mehr ueber die Datenstruktur, kann man einen sinnvolleren Namen waehlen.

2. Man will leicht auf Veraenderungen reagieren. Das geht weit einfacher (und sicherer!) ueber Funktion, die man fuer den uniformen Zugriff benutzt als ueber 20 Codestellen, die _scheinbar_ das gleiche tun.
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

cofi hat geschrieben:1. Man will die _Absicht_ explizit machen (das ist vorallem mit "explicit is better than implicit" gemeint, nicht den Code explizit machen)
Ich sehe da die Gefahr, dass man Fehler versteckt.

Wenn ein fehlerhafter Codeabschnitt die Absicht trotzdem gut ausdrückt, sucht man dort vielleicht garnicht nach dem Fehler. Anders ist es wenn der Code einfach gut seine Funktionsweise verdeutlicht. Z. B. in for-Schleifen würde ich häufiger

Code: Alles auswählen

for objekt in ...:
schreiben statt

Code: Alles auswählen

for konkrete_Unterstellung in hier_befindet_sich_vielleicht_was_ganz_anderes_drin:
cofi hat geschrieben:2. Man will leicht auf Veraenderungen reagieren. Das geht weit einfacher (und sicherer!) ueber Funktion, die man fuer den uniformen Zugriff benutzt als ueber 20 Codestellen, die _scheinbar_ das gleiche tun.
Dagegen sage ich ja allgemein garnichts, nur in diesem Fall halte ich es für übertrieben.

@EyDu
Wir haben wohl etwas aneinandervorbeigeredet. Was ich noch nicht verstehe ist, was gegen

Code: Alles auswählen

def key_function(x):
    return itemgetter(1)
spricht?
BlackJack

@Aries: Wenn das:

Code: Alles auswählen

get_number_i_defined_earlier = itemgetter(1)
get_defnum = itemgetter(1)
``get_second = itemgetter(1)`` ersetzen soll, dann muss man doch erst recht das machen was Du nicht so gut findest: Die Definition suchen weil der Funktionsname nichts darüber aussagt welches Element da eigentlich geholt wird. Das war ja einer der Gründe warum ich überhaupt einen Namen dafür eingeführt habe. Es ging darum einer abstrakter formulierten Operation einen konkreteren, verständlicheren Namen zu geben. Das ist alles andere als „sinnlos”, sondern es hilft beim Verständnis des Programms. Da hast Du aber anscheinend nichts übrig für wie Dein Beispiel mit der ``for``-Schleife zeigt. Was mir echt Angst macht. Ich würde keinen Code von Dir warten wollen, wenn da alles so generisch wie möglich benannt ist, damit man ja nichts falsch benennt.

Wie kann ein Code seine Funktionsweise gut verdeutlichen, wenn die Namen so generisch wie möglich gewählt werden? Wenn alles nur `obj`, `thing`, `sequence`, `container` und so weiter heisst, ohne mit den Namen deutlich zu machen was die Werte hinter den Namen im Kontext des Problems was gelöst wird bedeuten, hat man doch keine Chance zu verstehen was das Programm eigentlich macht. Man ist beim lesen ständig mit raten beschäftigt was da eigentlich semantisch passiert. Zumal ich ein bisschen glaube Du trollst hier, denn ich kann mir überhaupt nicht vorstellen, dass man das tatsächlich durchziehen kann, denn spätestens bei Funktions- und Methodennamen und Argumenten würde ich Schwierigkeiten bekommen so ein irrwitziges Benennungsschema durchzuziehen. Es sei denn man fängt an Funktionen tatsächlich mit Namen wie `blub` zu benennen. Dann wird es aber langsam absurd.

Code: Alles auswählen

for objekt in ...:

# statt

for konkrete_Unterstellung in hier_befindet_sich_vielleicht_was_ganz_anderes_drin:
Das schützt überhaupt nicht vor dem „Fehler” wie Du behauptest. Wenn im unteren Beispiel der Name nicht zu den Elementen im iterierbaren Objekt passt, dann machst Du als Programmierer eine falsche Annahme über die Elemente und behandelst sie im Schleifenkörper höchstwahrscheinlich falsch. Dadurch ändert sich nichts auf magische Weise wenn Du den generischen, und damit automatisch immer richtigen Namen `objekt` für die Elemente verwendest — im Schleifenkörper wird immer noch das falsche mit den Elementen gemacht.

Versetzen wir uns in die Lage von jemandem der den Fehler finden soll. Der sieht ``for objekt in container:`` im Vergleich zu z.B. ``for parrot in parrots:``, lässt sich mit `type()` mal den Typ von der Schleifenvariablen ausgeben und bekommt 'Cat' als Ergebnis. In welchem der beiden Fälle weiss er wohl sofort dass hier der Typ falsch ist, und in welchem muss er weiterforschen‽

Gegen

Code: Alles auswählen

def key_function(x):
    return itemgetter(1)
spricht, dass die Funktion keinen Sinn macht. Das `x` wird nicht verwendet.

Gegen

Code: Alles auswählen

def key_function(x):
    return get_second(x)

# bzw.

def key_function(x):
    return get_first(x)
von EyDu spricht IMHO dass es unnötig kompliziert ist. Ich hätte das jedenfalls so ausgedrückt:

Code: Alles auswählen

key_function = get_second

# bzw.

key_function = get_first
Kommt auf das selbe hinaus, ohne dass man noch eine zusätzliche Funktion erstellen muss.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Aries hat geschrieben:
cofi hat geschrieben:1. Man will die _Absicht_ explizit machen (das ist vorallem mit "explicit is better than implicit" gemeint, nicht den Code explizit machen)
Ich sehe da die Gefahr, dass man Fehler versteckt.
Das nennt sich Abstraktion. Wenn du _damit_ argumentierst, bist du in allen Sprachen ueber Assembler falsch. Mit modernen CPUs sogar damit.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

`get_second()` ist einfach eine Spezialisierung von `itemgetter(i)`. Ich finde den Wiedererkennungswert von `get_second()` vollkommen in Ordnung. Klar wird sich der Funktionsname ändern, wenn plötzlich das erste Element anstatt des zweiten Elements zurückgeliefert wird. Das haben Spezialisierungen nun mal so an sich. Ich denke, `get_second()` ist hier mit Sicherheit auch nur als interne Hilfsfunktion gedacht und nichts, was man unbedingt über eine öffentliche Schnittstelle bekannt machen möchte. Und für den internen Gebrauch sollte man es IMHO nicht übertreiben mit der Abstraktion und sozusagen auch mal Fünfe gerade sein lassen, sofern es hilfreich ist. Wenn man jetzt ganz kleinlich sein will, dann könnte man höchstens noch `get_second()` im Namensraum der `main()`-Funktion unterbringen, solange man weiß, dass es außerhalb der `main()`-Funktion nicht benutzt werden soll. Oder man verpasst der Funktion auf Modulebene einen führenden Unterstrich.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Aries:
Klar kannst Du auch einfach itemgetter(1) direkt nutzen, wenn Du befürchtest, dass ein Bezeichner den möglicherweise geänderten "Inhalt" nicht widerspiegelt. Wenn Du eine Kiste mit "Hühnergötter von der Ostsee" beschriftest und Fotos vom letzten Toscanaurlaub reinpackst - wie würdest Du damit verfahren? Die Sachen niemals in eine Kiste packen, weil die Beschriftung falsch sein könnte?
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

BlackJack hat geschrieben:@Aries: Wenn das:

Code: Alles auswählen

get_number_i_defined_earlier = itemgetter(1)
get_defnum = itemgetter(1)
``get_second = itemgetter(1)`` ersetzen soll, dann muss man doch erst recht das machen was Du nicht so gut findest: Die Definition suchen weil der Funktionsname nichts darüber aussagt welches Element da eigentlich geholt wird.
Mein Argument scheint nicht durchzudringen, vielleicht beachte ich auch einfach etwas nicht, worauf man erst aufmerksam wird, wenn man länger mit Python programmiert hat, aber ich versuche es nochmal:

Entweder get_second/get_defnum/get_wieauchimmer wird während der Programmentwicklung verändert. In dem Falle sollte man sich nicht auf den Namen verlassen, sondern vielmehr sollte der Name die Veränderlichkeit andeuten.

Oder get_second/get_defnum/get_wieauchimmer wird während der Programmentwicklung nicht verändert. In dem Falle ist der Name überflüssig und man kann stattdessen direkt das schreiben als was er definiert ist.
BlackJack hat geschrieben:Das war ja einer der Gründe warum ich überhaupt einen Namen dafür eingeführt habe. Es ging darum einer abstrakter formulierten Operation einen konkreteren, verständlicheren Namen zu geben. Das ist alles andere als „sinnlos”, sondern es hilft beim Verständnis des Programms.
Um ein Programm zu verstehen, könnte man auch ein Buch darüber schreiben. Aber hier geht es doch auch darum, zu lernen, wie man ein Programm schreibt. Und dazu muss man sich nunmal auf die Programmiersprache und damit programmiersprachliche Ausdrücke wie itemgetter(1) herablassen.
BlackJack hat geschrieben:Da hast Du aber anscheinend nichts übrig für wie Dein Beispiel mit der ``for``-Schleife zeigt. Was mir echt Angst macht. Ich würde keinen Code von Dir warten wollen, wenn da alles so generisch wie möglich benannt ist, damit man ja nichts falsch benennt.
Vielleicht irre ich mich hier, mit einer for-Schleife dieser Art habe ich bislang kaum Erfahrung.
BlackJack hat geschrieben: Das schützt überhaupt nicht vor dem „Fehler” wie Du behauptest. Wenn im unteren Beispiel der Name nicht zu den Elementen im iterierbaren Objekt passt, dann machst Du als Programmierer eine falsche Annahme über die Elemente und behandelst sie im Schleifenkörper höchstwahrscheinlich falsch. Dadurch ändert sich nichts auf magische Weise wenn Du den generischen, und damit automatisch immer richtigen Namen `objekt` für die Elemente verwendest — im Schleifenkörper wird immer noch das falsche mit den Elementen gemacht.
Ich meine nur, das man so vielleicht leichter auf den Fehler kommt.
BlackJack hat geschrieben: Versetzen wir uns in die Lage von jemandem der den Fehler finden soll. Der sieht ``for objekt in container:`` im Vergleich zu z.B. ``for parrot in parrots:``, lässt sich mit `type()` mal den Typ von der Schleifenvariablen ausgeben und bekommt 'Cat' als Ergebnis. In welchem der beiden Fälle weiss er wohl sofort dass hier der Typ falsch ist, und in welchem muss er weiterforschen‽
parrot und parrots sind vielleicht etwas verwechselbar. Meine Idee wäre hier: "for objekt in parrots:"

Nehmen wir an, man will prüfen ob sich in parrots nur Papageien befinden, dann wäre es etwas widersinnig, das zu prüfende schon vor der Prüfung parrot zu nennen.

Nehmen wir an, der dem for-Ausdruck untergeordnete Code-Abschnitt ist lang, dann ist es womöglich in ihm weiter unten nicht sofort ersichtlich, worum es sich bei parrot handelt. Wenn man hingegen in gewissen Fällen in for-Ausdrücken einheitlich objekt schreiben würde, wüsste man weiter unten bei objekt sofort, dass es sich um den Namen aus dem for-Ausdruck handelt.

Aber wie gesagt, dabei habe ich noch kaum Erfahrungen gesammelt.
BlackJack

@Aries: Ich finde einen Namen der besser beschreibt was eine Funktion tut oder ein Wert bedeutet nicht überflüssig. Und insbesondere ist ein Name nicht automatisch überflüssig nur weil der Ausdruck der zu dem Wert führt nicht besonders kompliziert ist.

Auf ``itemgetter(1)`` muss man sich nur ”herablassen” wenn das Ergebnis davon nicht vorher an einen guten Namen gebunden hat.

`parrot` und `parrots` mag zwar leicht verwechselbar sein, aber dieses Muster ist üblich. Auf das Detail ob ein Namen Einzahl oder Mehrzahl beschreibt sollte man achten können, denn auch in der natürlichen Sprache wird das so verwendet.

Wenn man annimmt das sich in `parrots` nur Papageien befinden, dann wäre es widersinnig die Objekte daraus *nicht* `parrot` zu nennen. Eine Typprüfung wäre in einer Sprache mit „duck typing” auch nichts was man normalerweise macht. Man benutzt den `parrot` einfach als wäre er einer, auch wenn er keiner ist. Solange er sich im gegebenen Kontext wie einer verhält ist es egal welchen Typ er tatsächlich hat.

Und natürlich ist es auch in einem langen Schleifenkörper klar um was es sich bei `parrot` handelt. Wenn es das nicht mehr ist, dann ist das Problem nicht der Name sondern die Länge des Schleifenkörpers wo man beim Lesen offenbar vergessen kann über was die Schleife eigentlich iteriert. Da hilft dann auch kein anderer Name und `objekt` schon gar nicht, denn dann weiss man *weder* dass es die Schleifenvariable ist, *noch* was der Wert eigentlich bedeutet. Der kann dann ja *alles* sein, weil in Python alles ein Objekt ist, was man an einen Namen binden kann.

Zur Länge: Faustregel für die Länge von Funktionen oder Methoden sind maximal 25 bis 50 Zeilen. Mit entsprechender Auswirkung auf die Länge von darin enthaltenen Schleifen.
Aries
User
Beiträge: 51
Registriert: Mittwoch 21. August 2013, 01:19

Zumindest verstehe ich jetzt die Denkweise etwas besser.

Für die for-Schleife hätte ich noch folgende Alternative anzubieten:

"for p in parrots:"

- p ist nicht mit parrots verwechselbar
- p ist kurz
- p ist als Objekt einer Iteration überall auch in einer langen Schleife sofort zu erkennen, sofern im Code in allen analogen Fällen ebenfalls einstellige Namen für so etwas verwendet werden.

Ein Problem ist sonst auch dass sich Einzahl und Mehrzahl nicht immer und manchmal nur durch Umlaute voneinander unterscheiden, sofern man nicht für alles englische Bezeichnungen verwenden will (und selbst da gibt es auch Fälle wie "woman", "women").
Antworten