Dynamic Programming met Python. Deel 2: rich comparison.

Monday 28 March 2011, 00:17:00 | python

Interessante voorbeelden van Dynamic Programming met Python.

  1. inleiding & de Fibonacci reeks; →Dynamic Programming met Python. Deel 1: fibonacci.
  2. rich comparison.

Je kunt een eigen ordening definieren tussen objecten van een zelfgemaakte class. Daarmee kun je de sorteervolgorde bepalen, en ook zorgen dat vergelijkingen zoals obj1 <= obj2 een zinvol resultaat geven. Voorheen kon je dat redelijk eenvoudig doen door __cmp__ te implementeren in je class, maar dat is deprecated omdat Python nu Rich Comparison semantics heeft. Helaas is daarvan het gevolg dat als je volledig correct werkende comparisons wilt hebben (met eigen gedrag), je alle rich comparisons methods moet definieren. Dus: __lt__, __le__, __gt__, __ge__, __eq__, __ne__. Dat is vervelend en foutgevoelig.

Stel we maken een Student class als volgt, waarbij we willen dat je 'm kan sorteren op naam, zonder dat we verschil willen tussen hoofd- en kleine letters:

class Student(object):
    def __init__(self, name):
        self.name=name
    def __eq__(self, other):
        return self.name.lower()==other.name.lower()
    def __lt__(self, other):
        return self.name.lower() < other.name.lower()

assert Student("Zoe") >= Student("Henry")    # simpele check faalt

Dit is een aardig begin maar de assert faalt, dus deze code is niet voldoende om alle comparisons correct te laten zijn!

Lees verder om te zien hoe je dit in Python met 1 regel code extra kunt oplossen.

In het bovenstaande voorbeeld zal Python 3.x een exception gooien zodra je Student objecten met elkaar gaat vergelijken: TypeError: unorderable types. Python 2.x doet dat niet, maar geeft soms een verkeerde uitkomst uit de vergelijking omdat de betreffende rich comparison operator niet geimplementeerd is en dan het verkeerde standaardgedrag vertoond (zoals in de assert hierboven).

functools.total_ordering decorator

Normaal gesproken moeten we dit dus fixen door de overgebleven 4 rich comparison operators ook te definieren. Maar er is een simpele decorator in de standard library die ons een hoop werk kan besparen: functools.total_ordering. Je hoeft zelf alleen maar een werkende __eq__ te maken plus 1 andere comparison operator, en de decorator voegt de ontbrekende dan toe (met het juiste gedrag):

from functools import total_ordering

@total_ordering
class Student(object):
    def __init__(self, name):
        self.name=name
    def __eq__(self, other):
        return self.name.lower()==other.name.lower()
    def __lt__(self, other):
        return self.name.lower() < other.name.lower()

assert Student("Zoe") >= Student("Henry")    # simpele check

s1=Student("John")
s2=Student("Henry")
s3=Student("Anna")
s4=Student("Zoe")
s5=Student("William")
s6=Student("Henry")  # tweede Henry
s7=Student("william")  # william met kleine letter
s8=Student("Aaron")
s9=Student("aaron")

students=[s1,s2,s3,s4,s5,s6,s7,s8,s9]

# dit werkt wel altijd omdat sorted alleen __lt__ nodig heeft
for s in sorted(students):
    print(s.name)

# John gelijk aan zichzelf maar ongelijk aan Henry
assert s1==s1
assert not(s1!=s1)
assert s1!=s2
assert not(s1==s2)

# william gelijk aan William
assert s7==s5
assert s7<=s5
assert s7>=s5

# Zoe is groter of gelijk aan allemaal
for s in students:
    assert s4>=s

# aaron is kleiner of gelijk aan allemaal
for s in students:
    assert s9<=s

Alles in één klap werkend door slechts 1 extra regel code. En het voordeel is ook dat als iemand je code bekijkt, dat hij/zij maar 1 comparison methode uit je class hoeft te snappen en dan kan zien "oh, de andere worden door de standard lib geregeld, dus de ordering is in orde". Als je ze allemaal met de hand zou uitschrijven moet je 6 methods controleren...

People have replied:

Youngy — http://www.google.com/

2011-05-10 12:16:00

AFAICT you've coevred all the bases with this answer!