Sep-15-2016, 11:04 PM
Just as we can override ==, we can override the other boolean comparisons:
Let's take another case that is perhaps more subtle. len(x) can be overridden with x.__len__. Why did I write a length method for the Vector class instead of using __len__? Because "len" in Python isn't "length" in mathematics. In Python, len is for containers, such as lists, dictionaries, and sets, and tells how many items are in the container. So __len__ for our Vector class isn't really meaningful, and if we really wanted to do it, __len__ would always return 2 for the two dimensions.
__ge__ overrides >= __gt__ overrides > __le__ overrides <= __lt__ overrides < __ne__ overrides !=Now let's say we had rewritten our __eq__ method to handle tuples of length 3 and some other odd cases. At that point our __eq__ method is starting to get rather complicated. Rewriting that code five more times to cover all the comparison operators is not only a pain, but it's a perfect opportunity to introduce bugs. Fortunately, the functools module provides a short cut named total_ordering. total_ordering is a class decorator. I'm not going to get into the details of decorators here, but basically they precede a definition and modify that definition. For our purposes we add a few lines to the start of our code:
import functools @functools.total_ordering class Vector(object): ...Now, total_ordering requires us to have defined __eq__ and one of __ge__, __gt__, __le__, __lt__. total_ordering then uses those two methods to define all of the other ones. So we need another method, I usually use __lt__:
def __lt__(self, other): """ Less than testing. Parameters: other: What to check less than against. (Vector) """ # vector to vector if isinstance(other, Vector): return self.length() < other.length() # vector to sequence elif isinstance(other, (tuple, list)): return self.length() < vector_length(other) # vector to anything else else: return NotImplemented def length(self): """ The length of the vector """ return (self.x ** 2 + self.y ** 2) ** 0.5 def vector_length(vector): """ Calculate the vector length for a list or tuple. Parameters: vector: A sequence of at least two numbers (list or tuple) """ return (vector[0] ** 2 + vector[1] ** 2) ** 0.5That's actually two more methods and a function external to the class. I wanted to order the vectors by length, but we're going to want to know the vector's length in other situations. So we might as well define a method for it. Since __lt__ uses length, it only has to be changed in one place if it needs to be changed. And if we are going to compare to sequences, we will need a way to calculate a length for them as well.
>>> a = Vector(8, 1) >>> b = Vector(3, 4) >>> a < b False >>> b < a True >>> a >= b True >>> a != b True >>> c = Vector(3, -4) >>> b < c False >>> b > c True >>> c < b FalseWTF? How can b be greater than c, but c not be less than b!? The problem is that we defined __eq__ and __lt__ in different terms. Our __eq__ method is effectively comparing magnitude and direction, while __lt__ is only testing magnitude. When total_ordering tries to combine them all, it makes some screwey results. This is the classic trap of operator overloading: doing things that don't really make sense. We don't generally order bound Cartesian vectors by magnitude, and forcing that on the system screwed things up.
Let's take another case that is perhaps more subtle. len(x) can be overridden with x.__len__. Why did I write a length method for the Vector class instead of using __len__? Because "len" in Python isn't "length" in mathematics. In Python, len is for containers, such as lists, dictionaries, and sets, and tells how many items are in the container. So __len__ for our Vector class isn't really meaningful, and if we really wanted to do it, __len__ would always return 2 for the two dimensions.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures