2009-08-06 9 views
1

Der Einfachheit halber sagen wir, ich habe eine Person-Klasse in Python. Diese Klasse hat Felder für Vorname, Nachname und Dob.Pythonic Vergleichsfunktionen

In einigen Situationen möchte ich Listen von Personen nach Nachnamen sortieren, gefolgt von Vorname gefolgt von DOB. In anderen Situationen möchte ich zuerst nach dob, dann nach lastname und schließlich nach vorname sortieren. Und manchmal möchte ich nur nach Vornamen sortieren.

Die naive Lösung für die erste Vergleichsfunktion zu schaffen wäre so etwas wie diese:

def comparepeople(person1, person2): 
    if cmp(person1.lastname, person2.lastname) == 0: 
    if cmp(person1.firstname, person2.firstname) == 0: 
     return cmp(person1.dob, person2.dob); 
    return cmp(person1.firstname, person2.firstname); 
    return cmp(person1.lastname, person2.lastname); 

Es scheinen würde, wie sollte es eine einfache Möglichkeit, zu Vergleichsfunktionen wie diese mit Hilfe eines Meta-Programmierung Ansatz, bei dem alle definieren Ich müsste die Feldnamen in der Reihenfolge ihrer Priorität angeben, anstatt diese sehr ausführlichen, hässlichen Vergleichsmethoden zu schreiben. Aber ich habe erst kürzlich angefangen, mit Python zu spielen und habe nichts gefunden, was ich beschrieben habe.

Die Frage ist also, was ist der pythonischste Weg, eine Vergleichsfunktion für eine Klasse mit mehreren vergleichbaren Bestandteilen zu schreiben?

+4

Als beiseite - entfernen diese Semikolons! Sie spielen keinen nützlichen Zweck und sind kein guter Python-Stil. –

Antwort

10

Wenn Sie wirklich eine Vergleichsfunktion möchten, können Sie

def comparepeople(p1, p2): 
    o1 = p1.lastname, p1.firstname, p1.dob 
    o2 = p2.lastname, p2.firstname, p2.dob 
    return cmp(o1,o2) 

Dies beruht auf verwenden können Tupelvergleich. Wenn Sie eine Liste sortieren möchten, sollten Sie nicht eine Vergleichsfunktion schreiben, obwohl, aber eine Schlüsselfunktion:

l.sort(key=lambda p:(p.lastname, p.firstname, p.dob)) 

Dies hat den Vorteil, dass es a) kürzer und b) schneller, da jeder Schlüssel bekommt nur einmal berechnet (anstatt dass Tonnen von Tupeln in der Vergleichsfunktion während des Sortierens erzeugt werden).

+1

Als Roberto darauf hinwies, Neds Antwort zu kommentieren, wäre 'l.sort (operator.attrgetter ('lastname', 'firstname', 'dob'))' sogar (marginal) schneller (wenn auch nicht kürzer ;-). –

4

Hier ist eine Art und Weise (vielleicht nicht die schnellsten):

def compare_people_flexibly(p1, p2, attrs): 
    """Compare `p1` and `p2` based on the attributes in `attrs`.""" 
    v1 = [getattr(p1, a) for a in attrs] 
    v2 = [getattr(p2, a) for a in attrs] 
    return cmp(v1, v2) 

def compare_people_firstname(p1, p2): 
    return compare_people_flexibly(p1, p2, ['firstname', 'lastname', 'dob']) 

def compare_people_lastname(p1, p2): 
    return compare_people_flexibly(p1, p2, ['lastname', 'firstname', 'dob']) 

Dies funktioniert, weil getattr verwendet werden kann Attribute durch eine Zeichenfolge mit dem Namen zu bekommen, und weil Python Listen vergleicht, wie man erwarten würde, auf der Basis Vergleich der ersten nicht gleichen Items.

Ein anderer Weg:

def compare_people_flexibly(p1, p2, attrs): 
    """Compare `p1` and `p2` based on the attributes in `attrs`.""" 
    for a in attrs: 
     c = cmp(getattr(p1, a), getattr(p2, a)) 
     if c: 
      return c 
    return 0 

Dies hat den Vorteil, dass es nicht zwei vollständige Liste der Attribute nicht bauen kann schneller so sein, wenn die Attributlisten lang sind, oder wenn viele Vergleiche vollständig auf dem ersten Attribute .

Schließlich ist, wie Martin erwähnt, können Sie eine Schlüsselfunktion benötigen, anstatt eine Vergleichsfunktion:

def flexible_person_key(attrs): 
    def key(p): 
     return [getattr(p, a) for a in attrs] 
    return key 

l.sort(key=flexible_person_key('firstname', 'lastname', 'dob')) 
+2

flexible_person_key = operator.attrgetter;) –

+0

Ja, seit Python 2.5 attrgetter kann mit mehreren Argumenten aufgerufen werden, um ein Tupel mit mehreren Attributen zu erhalten. –

+0

Das wusste ich nie! Danke für den Unterricht. –

0

können Sie die Vergleichsmethoden für die Klasse verwenden, finden Sie __cmp__ und die anderen reichen Vergleichsmethoden ...

+0

Der Punkt ist, ich brauche die Vergleichsfunktion, um in verschiedenen Situationen unterschiedlich zu sein, und selbst wenn ich __cmp__ auf Person implementieren würde, müsste ich immer noch etwas verwenden, was Ned für die Implementierung beschreibt. –