Getting Rich With Comparison Methods

Post on 30-Nov-2014

194 views 0 download

Tags:

description

The ins and outs of overriding comparison operators in Python, from a talk I gave at PyGotham 2014.

Transcript of Getting Rich With Comparison Methods

Getting Rich With Comparison MethodsMatt Story (@mscottstory) // PyGotham – August, 2014

eng.handshake.com@handshakeeng

$2.50

The most certain of all basic principles is that contradictory propositions are not true simultaneously.~ Aristotle -- Metaphisics

>>> from philosopher import Philosopher>>> aristotle = Philosopher.load(“Aristotle”)>>> aristotle == Philosopher.load(“Aristotle”)True

>>> from philosopher import Philosopher>>> aristotle = Philosopher.load(“Aristotle”)>>> aristotle == Philosopher.load(“Aristotle”)True>>> aristotle != Philosopher.load(“Aristotle”)True

How Am I Not Myself?

class Philosopher(JSONStore): '''JSONStore provides `load` and `save`''' store = './lyceum' dump_these = ('name', ‘rep’, 'works’)

def __init__(self, name, rep=5, works=tuple()): self.name = name self.rep = rep self.works = list(works) super(Philosopher, self).__init__(name.lower())

def __eq__(self, other): if isinstance(self, other.__class__) \ and hasattr(other, ‘id’): return self.id == other.id return NotImplemented

class Philosopher(JSONStore): '''JSONStore provides `load` and `save`''' store = './lyceum' dump_these = ('name', ‘rep’, 'works’)

def __init__(self, name, rep=5, works=tuple()): self.name = name self.rep = rep self.works = list(works) super(Philosopher, self).__init__(name.lower())

def __eq__(self, other): if isinstance(self, other.__class__) \ and hasattr(other, ‘id’): return self.id == other.id return NotImplemented

There are no implied relationships among the comparison operators. The truth of x == y does not imply that x != y is false. Accordingly, when defining __eq__(), one should also define __ne__() so that the operators will behave as expected.

5 Minutes Later …

class Philosopher(JSONStore): # ... snip def __eq__(self, other): if isinstance(self, other.__class__) \ and hasattr(other, ‘id’): return self.id == other.id return NotImplemented

def __ne__(self, other): is_eq = self.__eq__(other) if is_eq is NotImplemented: return is_eq return not is_eq

class Philosopher(JSONStore): # ... snip def __eq__(self, other): if isinstance(self, other.__class__) \ and hasattr(other, ‘id’): return self.id == other.id return NotImplemented

def __ne__(self, other): is_eq = self.__eq__(other) if is_eq is NotImplemented: return is_eq return not is_eq

>>> aristotle == Philosopher.load(“Aristotle”)True>>> aristotle != Philosopher.load(“Aristotle”)False

Aristotle

KANT

class Philosopher(JSONStore): # ... Snip def __gt__(self, other): if isinstance(self, other.__class__) \ and hasattr(other, 'rep'): return self.rep > other.rep return NotImplemented

>>> aristotle = Philosopher.load(‘Aristotle’)>>> kant = Philosopher.load(‘Kant’)>>> print aristotle.rep10>>> print kant.rep8>>> aristotle > kantTrue

>>> aristotle = Philosopher.load(‘Aristotle’)>>> kant = Philosopher.load(‘Kant’)>>> print aristotle.rep10>>> print kant.rep8>>> aristotle > kantTrue>>> kant < aristotleTrue

5 Seconds Later …There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather, __lt__() and __gt__() are each other’s reflection, __le__() and __ge__() are each other’s reflection, and __eq__() and __ne__() are their own reflection.

class Philosopher(JSONStore): # ... Snip def __gt__(self, other): print ’{}: called __gt__’.format(self.name) if isinstance(self, other.__class__) \ and hasattr(other, 'rep'): return self.rep > other.rep return NotImplemented

>>> aristotle > kantAristotle: called __gt__True>>> kant < aristotleAristotle: called __gt__True

NotImplementedThis type has a single value. There is a single object with this value … Rich comparison methods may return this value if they do not implement the operation for the operands provided. (The interpreter will then try the reflected operation, or some other fallback, depending on the operator.)

>>> "abc".__lt__(0)NotImplemented

Right-Side Reflection Returns

NotImplemented

Left-Side Method Is Defined

Yes

No

Left-Side Method Returns

NotImplemented

Yes

No

Return

Right-Side Reflection Is

Defined

Return

Yes

No

Return Default (is)

No

Aristotle

Foucault

class PostModernist(Philosopher): def __lt__(self, other): if isinstance(self, other.__class__): # with panopticon authority return False return NotImplemented

>>> from philosopher import PostModernist>>> foucault = PostModernist.load('Foucault')>>> foucault.rep6>>> aristotle.rep10>>> aristotle > foucault

>>> from philosopher import PostModernist>>> foucault = PostModernist.load('Foucault')>>> foucault.rep6>>> aristotle.rep10>>> aristotle > foucaultFalse

class PostModernist(Philosopher): def __lt__(self, other): print ’{}: __lt__ called'.format(self.name) if isinstance(self, other.__class__): return False return NotImplemented

>>> aristotle > foucaultFoucault: called __lt__False

import operator

assert operator.lt(kant, aristotle)assert operator.gt(aristotle, kant)assert operator.eq(kant, kant)assert operator.eq(aristotle, kant)

__eq____ne____lt____le____gt____ge__Lots of methods, means lots of redundant code

@functools.total_orderingclass Philosopher(JSONStore): # ... snip def __eq__(self, other): if isinstance(self, other.__class__) \ and hasattr(other, ‘id’): return self.id == other.id return NotImplemented

def __gt__(self, other): if isinstance(self, other.__class__) \ and hasattr(other, 'rep'): return self.rep > other.rep return NotImplemented def __ne__(self, other): # snip ...

class X(object): def __eq__(self, other): return lambda x: x == other

def __ne__(self, other): return lambda x: x != other

def __lt__(self, other): return lambda x: x < other

def __le__(self, other): return lambda x: x <= other

def __gt__(self, other): return lambda x: x > other

def __ge__(self, other): return lambda x: x >= other

x = X()

>>> filter(x == 2, range(5))[2]>>> filter(x > 1, range(5))[2, 3, 4]

Thanks@mscottstory