Getting Rich With Comparison Methods

40
Getting Rich With Comparison Methods Matt Story (@mscottstory) // PyGotham – August, 2014

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

Page 1: Getting Rich With Comparison Methods

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

Page 2: Getting Rich With Comparison Methods

eng.handshake.com@handshakeeng

Page 3: Getting Rich With Comparison Methods

$2.50

Page 4: Getting Rich With Comparison Methods

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

Page 5: Getting Rich With Comparison Methods

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

Page 6: Getting Rich With Comparison Methods

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

Page 7: Getting Rich With Comparison Methods

How Am I Not Myself?

Page 8: Getting Rich With Comparison Methods

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

Page 9: Getting Rich With Comparison Methods

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

Page 10: Getting Rich With Comparison Methods
Page 11: Getting Rich With Comparison Methods

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 …

Page 12: Getting Rich With Comparison Methods

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

Page 13: Getting Rich With Comparison Methods

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

Page 14: Getting Rich With Comparison Methods

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

Page 15: Getting Rich With Comparison Methods

Aristotle

KANT

Page 16: Getting Rich With Comparison Methods

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

Page 17: Getting Rich With Comparison Methods

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

Page 18: Getting Rich With Comparison Methods

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

Page 19: Getting Rich With Comparison Methods
Page 20: Getting Rich With Comparison Methods

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.

Page 21: Getting Rich With Comparison Methods

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

Page 22: Getting Rich With Comparison Methods

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

Page 23: Getting Rich With Comparison Methods

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.)

Page 24: Getting Rich With Comparison Methods

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

Page 25: Getting Rich With Comparison Methods

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

Page 26: Getting Rich With Comparison Methods

Aristotle

Foucault

Page 27: Getting Rich With Comparison Methods

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

Page 28: Getting Rich With Comparison Methods

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

Page 29: Getting Rich With Comparison Methods

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

Page 30: Getting Rich With Comparison Methods

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

Page 31: Getting Rich With Comparison Methods

>>> aristotle > foucaultFoucault: called __lt__False

Page 32: Getting Rich With Comparison Methods
Page 33: Getting Rich With Comparison Methods

import operator

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

Page 34: Getting Rich With Comparison Methods

__eq____ne____lt____le____gt____ge__Lots of methods, means lots of redundant code

Page 35: Getting Rich With Comparison Methods

@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 ...

Page 36: Getting Rich With Comparison Methods
Page 37: Getting Rich With Comparison Methods

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()

Page 38: Getting Rich With Comparison Methods

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

Page 39: Getting Rich With Comparison Methods
Page 40: Getting Rich With Comparison Methods

Thanks@mscottstory