5 ms·
Python is almost as bad as C++ when it comes to making custom classes sortable. In Python 2 there was had __cmp__, which was roughly the same as C++'s new spac
by comex 7y ago
Python is almost as bad as C++ when it comes to making custom classes sortable. In Python 2 there was had __cmp__, which was roughly the same as C++'s new spaceship operator, but Python 3 inexplicably removed it in favor of only having individual comparison operators like __eq__, __lt__, __gt__, etc. Oh, and functools.total_ordering, which lets you "only" define __lt__ and __eq__ and get the rest automatically implemented, but that's still two things to define when __cmp__ was only one.
- de_watcher 7y agoSo Python has reverted to the pre-C++20 state but without the ability to redefine other operators? Can you even make a proper custom float type in Python then?
- joshuamorton 7y agoYou can define all the operators explicitly if you wish. However, the common case is made easier, define only __eq__ and one of the others (__gt__ or __le__ usually) and @functools.total_ordering will derive all of the others for you.
- de_watcher 7y agoOkay, it's exactly the pre-C++20 state. That's actually funny.
- joshuamorton 7y agoI don't think that's true, python had no concept of strong vs. weak equality and ordering. cmp returned either -1, 0, or 1 (or at least, it was treated that way). There was no way to describe "unequal, but not orderable". That's the flaw which cpp avoids, although it remains to be seen if implementing complex spaceships is worth it. Or I guess, python is now in the pre-cpp20 state, but was never in the post-cpp20 state, so it didn't revert. Python's cmp was broken.
- de_watcher 7y agoWhat's not true? I didn't say that Python was in post-cpp20. Maybe you wanted to comment on my previous comment.
- joshuamorton 7y agoTo rewind a bit, you stated "So Python has reverted to the pre-C++20 state..." Which is the statement I originally took issue with.
- masklinn 7y agoIIRC the reason is that people would regularly screw up __cmp__ (despite the cmp built-in being available), it’s harder to fuck up simple boolean results that the any-integer-coerced-to-3-state of cmp. Python 3 also removed the “everything is comparable” misfeature of P2, providing way lower incentive to implement ordering: if your type is not orderable, a user knows to go with key functions.
- ioquatix 7y agoI don't trust someone who can't implement `__cmp__` to implement `__lt__` either.
- masklinn 7y agoExperience says you're way wrong, simply because combining a sequence of sub-lt calls is way simpler and shorter than combining a sequence of cmp: def __lt__(self, other): return self.a < other.a and self.b < other.b and self.c < other.c meanwhile def __cmp__(self, other): v = cmp(self.a, other.a) if v != 0: return v v = cmp(self.b, other.b) if v != 0: return v v = cmp(self.c, other.c) if v != 0: return v return 0 Rust (for instance) makes the latter less offensive (and error-prone) by providing built-in combinators: https://doc.rust-lang.org/std/cmp/enum.Ordering.html https://doc.rust-lang.org/std/cmp/enum.Ordering.html impl Ord for Thing { fn cmp(&self, other: &Self) -> Ordering { self.a.cmp(&self.a) .then(self.b.cmp(&other.b)) .then(self.c.cmp(&other.c)) } }
- BlackFingolfin 7y agoBut that first comparison function is usually not what you want, you want lexicographic ordering. This very example is in fact discussed in the article, as an example to how people often mess this up...
- masklinn 7y ago
- joshuamorton 7y agoThe issue is that __cmp__ wasn't "only one". For one thing, you generally want equality independent of ordering, so defining an explicit __eq__ that just handles equality is a decent idea. Otherwise either you define both cmp and eq, or you have your cmp including stuff like isinstance checks, in some cases, and having to decide if your custom class is more, or less, than a string (as masklinn notes below). Note: there's literally only bad answers here. Does cmp raise an exception? Yikes. Is MyCustomObject less than "hello world", or more, or does it depend on the internal state of MyCustomObject? Yikes, why do I have to choose? MyCustomObject isn't like "hello world". So you want eq, seperate from cmp. Then you still need a way to define ordering, so either write them all for weird cases, or just pick one and have total_ordering do the legwork.
- ioquatix 7y agoSo why not just define `__eq__` and `__cmp__`. It just seems to make so much more sense... and only `__eq__` only if you need it different from `__cmp__ == 0`. Removing `__cmp__` was a step backwards IMHO.
- joshuamorton 7y agoBecause requiring you to define eq is more correct. Equality and ordering are different concepts. If they wanted to keep cmp, the only correct choice would be to require eq if you define cmp. This again goes back to what masklinn said: a total ordering across all objects in python is a misfeature. The idea that `object() < 13 < "hello world" < 3+2j < [(frozenset(), frozenset())] < {type: int}` should evaluate to anything but a TypeError is horrifying! And once you make that decision, if an object implements cmp, it must implement eq (because the safe thing should be the default, and allowing people to implement cmp in isolation lets them shoot themselves in the foot). And at that point, cmp is more work to write than le or gt, so instead just implement eq, and then if you want it, le or gt + total_ordering, or if you need weird things implement each method individually. In practice, I've also found it much easier to reason about eq or le than cmp. People like to be tricky with cmp, and even when they don't, returning -1 or 1 is less explicit than just returning True or False.