Re: Why does __ne__ exist?

2018-01-08 Thread Chris Angelico
On Tue, Jan 9, 2018 at 3:25 AM, Cody Piersall  wrote:
>> Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
>> just like with __contains__. Go ahead: sell the notion of __ne__.
>> Pitch it, show why we absolutely need to allow this. Make sure you
>> mention the potential confusion when subclassing. Be sure to show why
>> it's okay for "not in" to force to boolean but "==" should allow any
>> return value.
>
> __ne__ and __eq__ are important for building mask arrays in NumPy,
> which allow complex indexing operations.  A lot of NumPy's design was
> inspired by MATLAB, so being able to index the same way as in MATLAB
> is a pretty killer feature.
>
> Indexing an array using mask arrays like this is idiomatic:
>
> some_arr = np.array([-1, 0, 1, 2, 3, 4, 5, 2, -1, 3, -1, 6, 7, 3])
> valid = some_arr[some_arr != -1]
>
> Anybody with familiarity with NumPy appreciates that this is possible.

I've used it, and I'm familiar with it, and I'm still not sure that I
appreciate it. But if it's there because of MATLAB, well, I guess
that's what people want?

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Antoon Pardon
Op 08-01-18 om 17:25 schreef Cody Piersall:
>> Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
>> just like with __contains__. Go ahead: sell the notion of __ne__.
>> Pitch it, show why we absolutely need to allow this. Make sure you
>> mention the potential confusion when subclassing. Be sure to show why
>> it's okay for "not in" to force to boolean but "==" should allow any
>> return value.
> 
> __ne__ and __eq__ are important for building mask arrays in NumPy,
> which allow complex indexing operations.  A lot of NumPy's design was
> inspired by MATLAB, so being able to index the same way as in MATLAB
> is a pretty killer feature.

They are only important if you find it necessary to build these mask
arrays wih an operator.

> Indexing an array using mask arrays like this is idiomatic:
> 
> some_arr = np.array([-1, 0, 1, 2, 3, 4, 5, 2, -1, 3, -1, 6, 7, 3])
> valid = some_arr[some_arr != -1]

I guess it was rather useful because list comprehension wasn't included
in the language at that moment, but I don't find it that much more useful
than:

   valid = somearr[[number != -1 for number in somearr]]

-- 
Antoon Pardon.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Cody Piersall
> Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
> just like with __contains__. Go ahead: sell the notion of __ne__.
> Pitch it, show why we absolutely need to allow this. Make sure you
> mention the potential confusion when subclassing. Be sure to show why
> it's okay for "not in" to force to boolean but "==" should allow any
> return value.

__ne__ and __eq__ are important for building mask arrays in NumPy,
which allow complex indexing operations.  A lot of NumPy's design was
inspired by MATLAB, so being able to index the same way as in MATLAB
is a pretty killer feature.

Indexing an array using mask arrays like this is idiomatic:

some_arr = np.array([-1, 0, 1, 2, 3, 4, 5, 2, -1, 3, -1, 6, 7, 3])
valid = some_arr[some_arr != -1]

Anybody with familiarity with NumPy appreciates that this is possible.

I imagine that ORMs like Django or SqlAlchemy also override __ne__ to
provide nice APIs.

Finally (and perhaps least imporant), there is a performance hit if
only allowing __eq__ and then taking its inverse.

Cody
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Antoon Pardon
Op 08-01-18 om 00:53 schreef Ethan Furman:
> On 01/07/2018 12:33 PM, Chris Angelico wrote:
>
> Actually, I think it is why it exists.  If I recall correctly, the
> addition of the six comparative operators* was added at the behest of
> the scientific/numerical community.

Which personnaly, I think was a mistake. I can understand it is useful
for the scientific/numerical community to compare vectors with each
other and get a vector of booleans. However how useful is it doing this
with the normal boolean operators, instead of calling a function?

And if doing it with an operator was so important, I think it would have
been better to introduce boxed operators, like [+], [<] ... where the
default behaviour would be an elementary wise application of the
non-boxed operator.

-- 
Antoon Pardon
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Oren Ben-Kiki
Good points. Well, this is pretty academic at this point - I don't think
anyone would seriously choose to obsolete __ne__, regardless of whether it
is absolutely necessary or not.

On Mon, Jan 8, 2018 at 4:51 PM, Thomas Jollans  wrote:

> On 2018-01-08 15:25, Oren Ben-Kiki wrote:
> > I don't see a case in IEEE where (x == y) != !(x != y).
> > There _is_ a case where (x != x) is true (when x is NaN), but for such an
> > x, (x == x) will be false.
> >
> > I am hard pressed to think of a case where __ne__ is actually useful.
>
> See my earlier email and/or PEP 207. (tl;dr: non-bool return values)
>
> >
> > That said, while it is true you only need one of (__eq__, __ne__), you
> > could make the same claim about (__lt__, __ge__) and (__le__, __gt__).
> > That is, in principle you could get by with only (__eq__, __le__, and
> > __ge__) or, if you prefer, (__ne__, __lt__, __gt__), or any other
> > combination you prefer.
>
> PEP 207: "The above mechanism is such that classes can get away with not
> implementing either __lt__ and __le__ or __gt__ and __ge__."
>
>
> >
> > Or you could go where C++ is doing and say that _if_ one specifies a
> single
> > __cmp__ method, it should return one of LT, EQ, GT, and this will
> > automatically give rise to all the comparison operators.
>
> This used to be the case. (from version 2.1 to version 2.7, AFAICT)
>
>
> >
> > "Trade-offs... trafe-offs as far as the eye can see" ;-)
> >
> >
> > On Mon, Jan 8, 2018 at 4:01 PM, Thomas Nyberg  wrote:
> >
> >> On 01/08/2018 12:36 PM, Thomas Jollans wrote:
> >>>
> >>> Interesting sentence from that PEP:
> >>>
> >>> "3. The == and != operators are not assumed to be each other's
> >>> complement (e.g. IEEE 754 floating point numbers do not satisfy this)."
> >>>
> >>> Does anybody here know how IEE 754 floating point numbers need __ne__?
> >>
> >> That's very interesting. I'd also like an answer to this. I can't wrap
> >> my head around why it would be true. I've just spent 15 minutes playing
> >> with the interpreter (i.e. checking operations on 0, -0, 7,
> >> float('nan'), float('inf'), etc.) and then also reading a bit about IEEE
> >> 754 online and I can't find any combination of examples where == and !=
> >> are not each others' complement.
> >>
> >> Cheers,
> >> Thomas
> --
> https://mail.python.org/mailman/listinfo/python-list
>
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Thomas Jollans
On 2018-01-08 15:25, Oren Ben-Kiki wrote:
> I don't see a case in IEEE where (x == y) != !(x != y).
> There _is_ a case where (x != x) is true (when x is NaN), but for such an
> x, (x == x) will be false.
> 
> I am hard pressed to think of a case where __ne__ is actually useful.

See my earlier email and/or PEP 207. (tl;dr: non-bool return values)

> 
> That said, while it is true you only need one of (__eq__, __ne__), you
> could make the same claim about (__lt__, __ge__) and (__le__, __gt__).
> That is, in principle you could get by with only (__eq__, __le__, and
> __ge__) or, if you prefer, (__ne__, __lt__, __gt__), or any other
> combination you prefer.

PEP 207: "The above mechanism is such that classes can get away with not
implementing either __lt__ and __le__ or __gt__ and __ge__."


> 
> Or you could go where C++ is doing and say that _if_ one specifies a single
> __cmp__ method, it should return one of LT, EQ, GT, and this will
> automatically give rise to all the comparison operators.

This used to be the case. (from version 2.1 to version 2.7, AFAICT)


> 
> "Trade-offs... trafe-offs as far as the eye can see" ;-)
> 
> 
> On Mon, Jan 8, 2018 at 4:01 PM, Thomas Nyberg  wrote:
> 
>> On 01/08/2018 12:36 PM, Thomas Jollans wrote:
>>>
>>> Interesting sentence from that PEP:
>>>
>>> "3. The == and != operators are not assumed to be each other's
>>> complement (e.g. IEEE 754 floating point numbers do not satisfy this)."
>>>
>>> Does anybody here know how IEE 754 floating point numbers need __ne__?
>>
>> That's very interesting. I'd also like an answer to this. I can't wrap
>> my head around why it would be true. I've just spent 15 minutes playing
>> with the interpreter (i.e. checking operations on 0, -0, 7,
>> float('nan'), float('inf'), etc.) and then also reading a bit about IEEE
>> 754 online and I can't find any combination of examples where == and !=
>> are not each others' complement.
>>
>> Cheers,
>> Thomas
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Oren Ben-Kiki
Ugh, right, for NaN you can have (x < y) != (x >= y) - both would be false
if one of x and y is a NaN.

But __ne__ is still useless ;-)

On Mon, Jan 8, 2018 at 4:36 PM, Thomas Nyberg  wrote:

> On 01/08/2018 03:25 PM, Oren Ben-Kiki wrote:
> > I am hard pressed to think of a case where __ne__ is actually useful.
>
> Assuming you're talking about a case specifically for IEEE 754, I'm
> starting to agree. In general, however, it certainly is useful for some
> numpy objects (as mentioned elsewhere in this thread).
>
> > That said, while it is true you only need one of (__eq__, __ne__), you
> > could make the same claim about (__lt__, __ge__) and (__le__, __gt__).
> > That is, in principle you could get by with only (__eq__, __le__, and
> > __ge__) or, if you prefer, (__ne__, __lt__, __gt__), or any other
> > combination you prefer.
>
> This isn't true for IEEE 754. For example:
>
> >>> float('nan') < 0
> False
> >>> float('nan') > 0
> False
> >>> float('nan') == 0
> False
>
> Also there are many cases where you don't have a < b OR a >= b. For
> example, subsets don't follow this.
>
> > "Trade-offs... trafe-offs as far as the eye can see" ;-)
>
> Yes few things in life are free. :)
> --
> https://mail.python.org/mailman/listinfo/python-list
>
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Thomas Nyberg
On 01/08/2018 03:25 PM, Oren Ben-Kiki wrote:
> I am hard pressed to think of a case where __ne__ is actually useful.

Assuming you're talking about a case specifically for IEEE 754, I'm
starting to agree. In general, however, it certainly is useful for some
numpy objects (as mentioned elsewhere in this thread).

> That said, while it is true you only need one of (__eq__, __ne__), you
> could make the same claim about (__lt__, __ge__) and (__le__, __gt__).
> That is, in principle you could get by with only (__eq__, __le__, and
> __ge__) or, if you prefer, (__ne__, __lt__, __gt__), or any other
> combination you prefer.

This isn't true for IEEE 754. For example:

>>> float('nan') < 0
False
>>> float('nan') > 0
False
>>> float('nan') == 0
False

Also there are many cases where you don't have a < b OR a >= b. For
example, subsets don't follow this.

> "Trade-offs... trafe-offs as far as the eye can see" ;-)

Yes few things in life are free. :)
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Oren Ben-Kiki
I don't see a case in IEEE where (x == y) != !(x != y).
There _is_ a case where (x != x) is true (when x is NaN), but for such an
x, (x == x) will be false.

I am hard pressed to think of a case where __ne__ is actually useful.

That said, while it is true you only need one of (__eq__, __ne__), you
could make the same claim about (__lt__, __ge__) and (__le__, __gt__).
That is, in principle you could get by with only (__eq__, __le__, and
__ge__) or, if you prefer, (__ne__, __lt__, __gt__), or any other
combination you prefer.

Or you could go where C++ is doing and say that _if_ one specifies a single
__cmp__ method, it should return one of LT, EQ, GT, and this will
automatically give rise to all the comparison operators.

"Trade-offs... trafe-offs as far as the eye can see" ;-)


On Mon, Jan 8, 2018 at 4:01 PM, Thomas Nyberg  wrote:

> On 01/08/2018 12:36 PM, Thomas Jollans wrote:
> >
> > Interesting sentence from that PEP:
> >
> > "3. The == and != operators are not assumed to be each other's
> > complement (e.g. IEEE 754 floating point numbers do not satisfy this)."
> >
> > Does anybody here know how IEE 754 floating point numbers need __ne__?
>
> That's very interesting. I'd also like an answer to this. I can't wrap
> my head around why it would be true. I've just spent 15 minutes playing
> with the interpreter (i.e. checking operations on 0, -0, 7,
> float('nan'), float('inf'), etc.) and then also reading a bit about IEEE
> 754 online and I can't find any combination of examples where == and !=
> are not each others' complement.
>
> Cheers,
> Thomas
> --
> https://mail.python.org/mailman/listinfo/python-list
>
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Thomas Nyberg
On 01/08/2018 12:36 PM, Thomas Jollans wrote:
> 
> Interesting sentence from that PEP:
> 
> "3. The == and != operators are not assumed to be each other's
> complement (e.g. IEEE 754 floating point numbers do not satisfy this)."
> 
> Does anybody here know how IEE 754 floating point numbers need __ne__?

That's very interesting. I'd also like an answer to this. I can't wrap
my head around why it would be true. I've just spent 15 minutes playing
with the interpreter (i.e. checking operations on 0, -0, 7,
float('nan'), float('inf'), etc.) and then also reading a bit about IEEE
754 online and I can't find any combination of examples where == and !=
are not each others' complement.

Cheers,
Thomas
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-08 Thread Thomas Jollans
On 2018-01-08 01:31, breamore...@gmail.com wrote:
> On Monday, January 8, 2018 at 12:02:09 AM UTC, Ethan Furman wrote:
>> On 01/07/2018 12:33 PM, Chris Angelico wrote:
>>> On Mon, Jan 8, 2018 at 7:13 AM, Thomas Jollans wrote:
 On 07/01/18 20:55, Chris Angelico wrote:
> Under what circumstances would you want "x != y" to be different from
> "not (x == y)" ?

 In numpy, __eq__ and __ne__ do not, in general, return bools.

>>> a = np.array([1,2,3,4])
>>> b = np.array([0,2,0,4])
>>> a == b
 array([False,  True, False,  True], dtype=bool)
>>> a != b
 array([ True, False,  True, False], dtype=bool)
>>>
>>> Thanks, that's the kind of example I was looking for. Though numpy
>>> doesn't drive the core language development much, so the obvious next
>>> question is: was this why __ne__ was implemented, or was there some
>>> other reason? This example shows how it can be useful, but not why it
>>> exists.
>>
>> Actually, I think it is why it exists.  If I recall correctly, the addition 
>> of the six comparative operators* was added 
>> at the behest of the scientific/numerical community.
>>
>> --
>> ~Ethan~
>>
>> * Yeah, I can't remember the cool name for those six operators at the 
>> moment.  :(
> 
> The six rich comparison methods were added to 2.1 as a result of PEP 207, 
> which confirms that you're correct, they were added at the request of the 
> numpyites.

Interesting sentence from that PEP:

"3. The == and != operators are not assumed to be each other's
complement (e.g. IEEE 754 floating point numbers do not satisfy this)."

Does anybody here know how IEE 754 floating point numbers need __ne__?


> 
> --
> Kindest regards.
> 
> Mark Lawrence.
> 


-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
On Mon, Jan 8, 2018 at 4:21 PM, Ethan Furman  wrote:
> On 01/07/2018 04:57 PM, Chris Angelico wrote:
>>
>> On Mon, Jan 8, 2018 at 11:35 AM, Ben Finney wrote:
>>>
>>> Chris Angelico writes:
>
>
 Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
 just like with __contains__. Go ahead: sell the notion of __ne__.
 Pitch it, show why we absolutely need to allow this.
>>>
>>>
>>> I think “reject unless absolutely needed” is an unreasonably high bar,
>>> which would disqualify most Python language features. So I don't know
>>> why you expect this to be so especially strongly argued.
>>
>>
>> True, I exaggerated a bit. But do you think that, had __ne__ not
>> existed for years, its addition could be justified?
>
>
> Considering we just recently added a matrix-multiplication operator, yes.
>

That has approximately zero consequences on class authors. If you were
unaware of __matmul__, it wouldn't have the chance to randomly break
your __mul__ semantics. And even with that excellent backward
compatibility, it STILL took many years to get accepted.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Ethan Furman

On 01/07/2018 04:57 PM, Chris Angelico wrote:

On Mon, Jan 8, 2018 at 11:35 AM, Ben Finney wrote:

Chris Angelico writes:



Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
just like with __contains__. Go ahead: sell the notion of __ne__.
Pitch it, show why we absolutely need to allow this.


I think “reject unless absolutely needed” is an unreasonably high bar,
which would disqualify most Python language features. So I don't know
why you expect this to be so especially strongly argued.


True, I exaggerated a bit. But do you think that, had __ne__ not
existed for years, its addition could be justified?


Considering we just recently added a matrix-multiplication operator, yes.

--
~Ethan~

--
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Ben Finney
Chris Angelico  writes:

> On Mon, Jan 8, 2018 at 11:35 AM, Ben Finney via Python-list
>  wrote:
> > I think “reject unless absolutely needed” is an unreasonably high
> > bar, which would disqualify most Python language features. So I
> > don't know why you expect this to be so especially strongly argued.
>
> True, I exaggerated a bit. But do you think that, had __ne__ not
> existed for years, its addition could be justified?

I'm not the one making pronouncements on what would or would not be
allowed, in a counterfactual universe where things had been different
for so many years. So, because I don't need to speculate about that, I
won't :-)

-- 
 \   “Corporation, n. An ingenious device for obtaining individual |
  `\   profit without individual responsibility.” —Ambrose Bierce, |
_o__)   _The Devil's Dictionary_, 1906 |
Ben Finney

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Ethan Furman

On 01/07/2018 04:31 PM, breamore...@gmail.com wrote:

On Monday, January 8, 2018 at 12:02:09 AM UTC, Ethan Furman wrote:

On 01/07/2018 12:33 PM, Chris Angelico wrote:

On Mon, Jan 8, 2018 at 7:13 AM, Thomas Jollans wrote:

On 07/01/18 20:55, Chris Angelico wrote:



Under what circumstances would you want "x != y" to be different from
"not (x == y)" ?


In numpy, __eq__ and __ne__ do not, in general, return bools.


a = np.array([1,2,3,4])
b = np.array([0,2,0,4])
a == b

array([False,  True, False,  True], dtype=bool)

a != b

array([ True, False,  True, False], dtype=bool)


Thanks, that's the kind of example I was looking for. Though numpy
doesn't drive the core language development much, so the obvious next
question is: was this why __ne__ was implemented, or was there some
other reason? This example shows how it can be useful, but not why it
exists.


Actually, I think it is why it exists.  If I recall correctly, the addition of 
the six comparative operators* was added
at the behest of the scientific/numerical community.

--
~Ethan~

* Yeah, I can't remember the cool name for those six operators at the moment.  
:(


The six rich comparison methods were added to 2.1 as a result of PEP 207, which 
confirms that you're correct, they were added at the request of the numpyites.


Cool, thanks for tracking that down!

--
~Ethan~
--
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread breamoreboy
On Monday, January 8, 2018 at 12:02:09 AM UTC, Ethan Furman wrote:
> On 01/07/2018 12:33 PM, Chris Angelico wrote:
> > On Mon, Jan 8, 2018 at 7:13 AM, Thomas Jollans wrote:
> >> On 07/01/18 20:55, Chris Angelico wrote:
> >>> Under what circumstances would you want "x != y" to be different from
> >>> "not (x == y)" ?
> >>
> >> In numpy, __eq__ and __ne__ do not, in general, return bools.
> >>
> > a = np.array([1,2,3,4])
> > b = np.array([0,2,0,4])
> > a == b
> >> array([False,  True, False,  True], dtype=bool)
> > a != b
> >> array([ True, False,  True, False], dtype=bool)
> >
> > Thanks, that's the kind of example I was looking for. Though numpy
> > doesn't drive the core language development much, so the obvious next
> > question is: was this why __ne__ was implemented, or was there some
> > other reason? This example shows how it can be useful, but not why it
> > exists.
> 
> Actually, I think it is why it exists.  If I recall correctly, the addition 
> of the six comparative operators* was added 
> at the behest of the scientific/numerical community.
> 
> --
> ~Ethan~
> 
> * Yeah, I can't remember the cool name for those six operators at the moment. 
>  :(

The six rich comparison methods were added to 2.1 as a result of PEP 207, which 
confirms that you're correct, they were added at the request of the numpyites.

--
Kindest regards.

Mark Lawrence.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
On Mon, Jan 8, 2018 at 11:35 AM, Ben Finney via Python-list
 wrote:
> Chris Angelico  writes:
>
>> On Mon, Jan 8, 2018 at 10:55 AM, Ben Finney  
>> wrote:
>> > We've established that it is useful to allow data types to define
>> > their own meaning of “equal” and “not equal”, like many other
>> > operations. Is that not good enough reason to allow it still?
>>
>> The fact that container types can define "contains" but can't define
>> "doesn't contain", and that (as of Py3) there's proper default
>> handling, suggests that it's not as big a priority now.
>
> That is an inconsistency, I agree.
>
>> Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
>> just like with __contains__. Go ahead: sell the notion of __ne__.
>> Pitch it, show why we absolutely need to allow this.
>
> I think “reject unless absolutely needed” is an unreasonably high bar,
> which would disqualify most Python language features. So I don't know
> why you expect this to be so especially strongly argued.

True, I exaggerated a bit. But do you think that, had __ne__ not
existed for years, its addition could be justified?

>> Make sure you mention the potential confusion when subclassing.
>
> For example, that would alsop be a problem for multiple inheritance. Not
> “absolutely needed”, and high risk of confusion when subclassing. Do you
> think that multiple inheritance would thereby also not be allowed today?
>
> If you consider that a different case, why?

There's a LOT that you can do usefully with MI that you can't do
without it. Having spent a few years (many years ago) working with
Java, I appreciate the ability to inherit from more than one class.
Does it have to be done the way Python currently does it? No. But one
way or another, it's a massively useful feature.

(You're right that "absolutely needed" is too high a bar, but
hyperbole aside, I do think that MI hits a higher mark than __ne__
does.)

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread breamoreboy
On Sunday, January 7, 2018 at 7:55:57 PM UTC, Chris Angelico wrote:
> Whoops, premature send. Picking up from the last paragraph.
> 
> This is good. This is correct. For inequalities, you can't assume that
> >= is the exact opposite of < or the combination of < and == (for
> example, sets don't behave like numbers, so "x <= y" is very different
> from "x < y or x == y"). But the one that confuses me is != or __ne__.
> If you don't create it, you get default behaviour:
> 
> >>> class Ham:
> ... def __eq__(self, other):
> ... print("%s equals %s" % (self, other))
> ... return True
> ...
> >>> Ham() == 1
> <__main__.Ham object at 0x7fb7557c0278> equals 1
> True
> >>> 2 == Ham()
> <__main__.Ham object at 0x7fb7557c0278> equals 2
> True
> >>> Ham() != 3
> <__main__.Ham object at 0x7fb7557c0278> equals 3
> False
> >>> 4 != Ham()
> <__main__.Ham object at 0x7fb7557c0278> equals 4
> False
> >>> x = Ham()
> >>> x == x
> <__main__.Ham object at 0x7fb7557b80f0> equals <__main__.Ham object at
> 0x7fb7557b80f0>
> True
> >>> x != x
> <__main__.Ham object at 0x7fb7557b80f0> equals <__main__.Ham object at
> 0x7fb7557b80f0>
> False
> 
> Under what circumstances would you want "x != y" to be different from
> "not (x == y)" ? How would this make for sane behaviour? Even when
> other things go weird with equality checks, that basic parallel is
> always maintained:
> 
> >>> z = float("nan")
> >>> z == z
> False
> >>> z != z
> True
> 
> Python gives us a "not in" operator that uses __contains__ and then
> negates the result. There is no way for "x not in y" to be anything
> different from "not (x in y)", as evidenced by the peephole optimizer:
> 
> >>> dis.dis("x not in y")
>   1   0 LOAD_NAME0 (x)
>   2 LOAD_NAME1 (y)
>   4 COMPARE_OP   7 (not in)
>   6 RETURN_VALUE
> >>> dis.dis("not (x in y)")
>   1   0 LOAD_NAME0 (x)
>   2 LOAD_NAME1 (y)
>   4 COMPARE_OP   7 (not in)
>   6 RETURN_VALUE
> 
> So why isn't != done the same way? Is it historical?
> 
> ChrisA

I'd say this is certainly historical, remembering that in Python 2 you used to 
be able to compare all sorts of things, whereas in Python 3 you'll get:-

>>> 1 < "a"
Traceback (most recent call last):
  File "", line 1, in 
TypeError: '<' not supported between instances of 'int' and 'str'
>>> 

This seems to be confirmed by the following.  From the third paragraph at 
https://docs.python.org/2/reference/datamodel.html#object.__ne__ "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...".  
Compare that with the Python 3 equivalent "By default, __ne__() delegates to 
__eq__() and inverts the result unless it is NotImplemented. There are no other 
implied relationships among the comparison operators, for example, the truth of 
(xhttps://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Ben Finney via Python-list
Chris Angelico  writes:

> On Mon, Jan 8, 2018 at 10:55 AM, Ben Finney  
> wrote:
> > We've established that it is useful to allow data types to define
> > their own meaning of “equal” and “not equal”, like many other
> > operations. Is that not good enough reason to allow it still?
>
> The fact that container types can define "contains" but can't define
> "doesn't contain", and that (as of Py3) there's proper default
> handling, suggests that it's not as big a priority now.

That is an inconsistency, I agree.

> Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
> just like with __contains__. Go ahead: sell the notion of __ne__.
> Pitch it, show why we absolutely need to allow this.

I think “reject unless absolutely needed” is an unreasonably high bar,
which would disqualify most Python language features. So I don't know
why you expect this to be so especially strongly argued.

> Make sure you mention the potential confusion when subclassing.

For example, that would alsop be a problem for multiple inheritance. Not
“absolutely needed”, and high risk of confusion when subclassing. Do you
think that multiple inheritance would thereby also not be allowed today?

If you consider that a different case, why?

-- 
 \ “First they came for the verbs, and I said nothing, for verbing |
  `\weirds language. Then, they arrival for the nouns and I speech |
_o__)   nothing, for I no verbs.” —Peter Ellis |
Ben Finney

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Serhiy Storchaka

07.01.18 22:33, Chris Angelico пише:

On Mon, Jan 8, 2018 at 7:13 AM, Thomas Jollans  wrote:

On 07/01/18 20:55, Chris Angelico wrote:

Under what circumstances would you want "x != y" to be different from
"not (x == y)" ?


In numpy, __eq__ and __ne__ do not, in general, return bools.


a = np.array([1,2,3,4])
b = np.array([0,2,0,4])
a == b

array([False,  True, False,  True], dtype=bool)

a != b

array([ True, False,  True, False], dtype=bool)


Thanks, that's the kind of example I was looking for. Though numpy
doesn't drive the core language development much, so the obvious next
question is: was this why __ne__ was implemented, or was there some
other reason? This example shows how it can be useful, but not why it
exists.


AFAIK this was the main reason. This can be also used for creating queries.

NumPy inspired 4 or 5 core features which are rarely used outside of 
NumPy. They include the possibility of comparison operators to return 
non-booleans.


--
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
On Mon, Jan 8, 2018 at 10:55 AM, Ben Finney  wrote:
> Chris Angelico  writes:
>
>> So, yeah, sounds like it's basically historical. I'm still not sure
>> why it was done in the first place, but it looks like it's the sort of
>> thing that wouldn't be done now.
>
> I'm not understanding why you speculate that it wouldn't be done today.
>
> We've established that it is useful to allow data types to define their
> own meaning of “equal” and “not equal”, like many other operations. Is
> that not good enough reason to allow it still?

The fact that container types can define "contains" but can't define
"doesn't contain", and that (as of Py3) there's proper default
handling, suggests that it's not as big a priority now.

Let's put it this way. Suppose that __eq__ existed and __ne__ didn't,
just like with __contains__. Go ahead: sell the notion of __ne__.
Pitch it, show why we absolutely need to allow this. Make sure you
mention the potential confusion when subclassing. Be sure to show why
it's okay for "not in" to force to boolean but "==" should allow any
return value.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Ethan Furman

On 01/07/2018 12:33 PM, Chris Angelico wrote:

On Mon, Jan 8, 2018 at 7:13 AM, Thomas Jollans  wrote:

On 07/01/18 20:55, Chris Angelico wrote:

Under what circumstances would you want "x != y" to be different from
"not (x == y)" ?


In numpy, __eq__ and __ne__ do not, in general, return bools.


a = np.array([1,2,3,4])
b = np.array([0,2,0,4])
a == b

array([False,  True, False,  True], dtype=bool)

a != b

array([ True, False,  True, False], dtype=bool)


Thanks, that's the kind of example I was looking for. Though numpy
doesn't drive the core language development much, so the obvious next
question is: was this why __ne__ was implemented, or was there some
other reason? This example shows how it can be useful, but not why it
exists.


Actually, I think it is why it exists.  If I recall correctly, the addition of the six comparative operators* was added 
at the behest of the scientific/numerical community.


--
~Ethan~

* Yeah, I can't remember the cool name for those six operators at the moment.  
:(
--
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Ben Finney
Chris Angelico  writes:

> So, yeah, sounds like it's basically historical. I'm still not sure
> why it was done in the first place, but it looks like it's the sort of
> thing that wouldn't be done now.

I'm not understanding why you speculate that it wouldn't be done today.

We've established that it is useful to allow data types to define their
own meaning of “equal” and “not equal”, like many other operations. Is
that not good enough reason to allow it still?

-- 
 \  “Science is what we understand well enough to explain to a |
  `\  computer. Art is everything else we do.” —Donald Knuth, 1996 |
_o__)  |
Ben Finney

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
On Mon, Jan 8, 2018 at 8:06 AM,   wrote:
> From the third paragraph at 
> https://docs.python.org/2/reference/datamodel.html#object.__ne__ "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...".  Compare that with the Python 3 equivalent "By default, 
> __ne__() delegates to __eq__() and inverts the result unless it is 
> NotImplemented. There are no other implied relationships among the comparison 
> operators, for example, the truth of (x

Ah, I forgot to check the Py2 docs. So, yeah, sounds like it's
basically historical. I'm still not sure why it was done in the first
place, but it looks like it's the sort of thing that wouldn't be done
now.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
On Mon, Jan 8, 2018 at 9:32 AM, bartc  wrote:
> On 07/01/2018 21:51, Chris Angelico wrote:
>>> dis.dis("not (x in y)")


 1   0 LOAD_NAME0 (x)
 2 LOAD_NAME1 (y)
 4 COMPARE_OP   7 (not in)
>>>
>>>
>>>
>>> I get '4 COMPARE OP6 (in)' here. So they are distinct ops. 'not in'
>>> doesn't just call 'in', then apply 'not'. Not here anyway.
>>>
>>
>> The fact that your Python doesn't optimize it is actually beside the
>> point; if _any_ Python interpreter can optimize this down, it must be
>> semantically identical.
>
>
> Actually I didn't see the 'not' on the outside of the brackets. I thought
> the two expressions were 'not in' and 'in' and that you might have
> transcribed the '7 (not in)' part wrongly.

That's why I don't transcribe - I copy and paste. It's way WAY safer that way.

> But this reduction isn't necessarily an optimisation. It might just be a
> syntactical transformation from 'not (a in b)' to '(a not in b)'
>
> The disassembly for 'in' and 'not in' suggests that these are two
> independent operators, which could indeed have behaviours that are not
> complements of each other.

Uhm, if the peephole optimizer does a syntactical transformation, it
MUST retain the semantics. The disassembly for "in" and "not in" shows
that they are independent, but the disassembly for "not (x in y)"
proves that they are semantically linked.

> On the other hand, when you /did/ want to evaluate 'in' followed by 'not',
> then you want:
>
>not (a in b)# compare using 'not in'
>
> to do the same thing as:
>
>temp = a in b   # compare using 'in'
>not temp# apply unary not
>
> Then there might not be the freedom to have in/not in have independent
> behaviours.

And the whole point of my post is that there is no such freedom - that
"not in" MUST always give the exact converse of "in". (And if
__contains__ returns something other than a boolean, it is coerced
before the operator returns it.) Yet equality is not like that. Hence
my post.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread bartc

On 07/01/2018 21:51, Chris Angelico wrote:

On Mon, Jan 8, 2018 at 7:41 AM, bartc  wrote:



Maybe someone wants to do weird stuff with == that doesn't yield a true or
false result, so that you can't just reverse it for !=.

For example (perhaps this is similar to what was suggested in another post):

  (10,20,30) == (10,20,40)   yields  (1,1,0)
  (10,20,30) != (10,20,40)   yields  (0,0,1)



With tuples, I absolutely agree with Python's current behaviour: the
tuples you give are simply not equal. A tuple doesn't represent a
vector; it represents a specific collection of values, like the
coordinates of a point in 2D or 3D space. If you look at the two
points (1,5) and (3,5), they aren't "half equal". They're different
points, at different locations. They happen to have the same
elevation, but that's just a point of curiosity.


My (10,20,30) were meant to represent some user-defined type, not an 
ordinary tuple. And someone might intend that == operates on two 
instances of that type as thought they were vectors. Or any other kind 
of behaviour as I said.


But not necessarily some logical inverse of !=.


dis.dis("not (x in y)")


1   0 LOAD_NAME0 (x)
2 LOAD_NAME1 (y)
4 COMPARE_OP   7 (not in)



I get '4 COMPARE OP6 (in)' here. So they are distinct ops. 'not in'
doesn't just call 'in', then apply 'not'. Not here anyway.



The fact that your Python doesn't optimize it is actually beside the
point; if _any_ Python interpreter can optimize this down, it must be
semantically identical.


Actually I didn't see the 'not' on the outside of the brackets. I 
thought the two expressions were 'not in' and 'in' and that you might 
have transcribed the '7 (not in)' part wrongly.


But this reduction isn't necessarily an optimisation. It might just be a 
syntactical transformation from 'not (a in b)' to '(a not in b)'


The disassembly for 'in' and 'not in' suggests that these are two 
independent operators, which could indeed have behaviours that are not 
complements of each other.


On the other hand, when you /did/ want to evaluate 'in' followed by 
'not', then you want:


   not (a in b)# compare using 'not in'

to do the same thing as:

   temp = a in b   # compare using 'in'
   not temp# apply unary not

Then there might not be the freedom to have in/not in have independent 
behaviours.


--
bartc
--
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
On Mon, Jan 8, 2018 at 7:41 AM, bartc  wrote:
> On 07/01/2018 19:55, Chris Angelico wrote:
>
>> Under what circumstances would you want "x != y" to be different from
>> "not (x == y)" ? How would this make for sane behaviour?
>
>
> Presumably so that any behaviour any be programmed when overriding these
> operators.
>
> Maybe someone wants to do weird stuff with == that doesn't yield a true or
> false result, so that you can't just reverse it for !=.
>
> For example (perhaps this is similar to what was suggested in another post):
>
>  (10,20,30) == (10,20,40)   yields  (1,1,0)
>  (10,20,30) != (10,20,40)   yields  (0,0,1)
>
> Although here, you would probably define 'not' so that 'not (1,1,0)' does
> actually yield '(0,0,1)'.
>
> So clearly I need a weirder example.

With tuples, I absolutely agree with Python's current behaviour: the
tuples you give are simply not equal. A tuple doesn't represent a
vector; it represents a specific collection of values, like the
coordinates of a point in 2D or 3D space. If you look at the two
points (1,5) and (3,5), they aren't "half equal". They're different
points, at different locations. They happen to have the same
elevation, but that's just a point of curiosity.

>  Even when
>>
>> other things go weird with equality checks, that basic parallel is
>> always maintained:
>>
> z = float("nan")
> z == z
>>
>> False
>
> z != z
>>
>> True
>>
>> Python gives us a "not in" operator that uses __contains__ and then
>> negates the result. There is no way for "x not in y" to be anything
>> different from "not (x in y)", as evidenced by the peephole optimizer:
>>
> dis.dis("x not in y")
>>
>>1   0 LOAD_NAME0 (x)
>>2 LOAD_NAME1 (y)
>>4 COMPARE_OP   7 (not in)
>>6 RETURN_VALUE
>
> dis.dis("not (x in y)")
>>
>>1   0 LOAD_NAME0 (x)
>>2 LOAD_NAME1 (y)
>>4 COMPARE_OP   7 (not in)
>
>
> I get '4 COMPARE OP6 (in)' here. So they are distinct ops. 'not in'
> doesn't just call 'in', then apply 'not'. Not here anyway.
>

The fact that your Python doesn't optimize it is actually beside the
point; if _any_ Python interpreter can optimize this down, it must be
semantically identical. I did this on CPython 3.7, fwiw, but it
doesn't really matter.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread bartc

On 07/01/2018 19:55, Chris Angelico wrote:


Under what circumstances would you want "x != y" to be different from
"not (x == y)" ? How would this make for sane behaviour?


Presumably so that any behaviour any be programmed when overriding these 
operators.


Maybe someone wants to do weird stuff with == that doesn't yield a true 
or false result, so that you can't just reverse it for !=.


For example (perhaps this is similar to what was suggested in another post):

 (10,20,30) == (10,20,40)   yields  (1,1,0)
 (10,20,30) != (10,20,40)   yields  (0,0,1)

Although here, you would probably define 'not' so that 'not (1,1,0)' 
does actually yield '(0,0,1)'.


So clearly I need a weirder example.


 Even when

other things go weird with equality checks, that basic parallel is
always maintained:


z = float("nan")
z == z

False

z != z

True

Python gives us a "not in" operator that uses __contains__ and then
negates the result. There is no way for "x not in y" to be anything
different from "not (x in y)", as evidenced by the peephole optimizer:


dis.dis("x not in y")

   1   0 LOAD_NAME0 (x)
   2 LOAD_NAME1 (y)
   4 COMPARE_OP   7 (not in)
   6 RETURN_VALUE

dis.dis("not (x in y)")

   1   0 LOAD_NAME0 (x)
   2 LOAD_NAME1 (y)
   4 COMPARE_OP   7 (not in)


I get '4 COMPARE OP6 (in)' here. So they are distinct ops. 'not in' 
doesn't just call 'in', then apply 'not'. Not here anyway.


--
bartc
--
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
On Mon, Jan 8, 2018 at 7:13 AM, Thomas Jollans  wrote:
> On 07/01/18 20:55, Chris Angelico wrote:
>> Under what circumstances would you want "x != y" to be different from
>> "not (x == y)" ?
>
> In numpy, __eq__ and __ne__ do not, in general, return bools.
>
 a = np.array([1,2,3,4])
 b = np.array([0,2,0,4])
 a == b
> array([False,  True, False,  True], dtype=bool)
 a != b
> array([ True, False,  True, False], dtype=bool)

Thanks, that's the kind of example I was looking for. Though numpy
doesn't drive the core language development much, so the obvious next
question is: was this why __ne__ was implemented, or was there some
other reason? This example shows how it can be useful, but not why it
exists.

 not (a == b)
> Traceback (most recent call last):
>   File "", line 1, in 
> ValueError: The truth value of an array with more than one element is
> ambiguous. Use a.any() or a.all()

Which means that this construct is still never going to come up in good code.

 ~(a == b)
> array([ True, False,  True, False], dtype=bool)

>
> I couldn't tell you why this was originally allowed, but it does turn
> out to be strangely useful. (As far as the numpy API is concerned, it
> would be even nicer if 'not' could be overridden, IMHO)

I'm glad 'not' can't be overridden; it'd be too hard to reason about a
piece of code if even the basic boolean operations could change. If
you want overridables, you have &|~ for the bitwise operators (which
is how numpy does things).

Has numpy ever asked for a "not in" dunder method (__not_contained__
or something)? It's a strange anomaly that "not (x in y)" can be
perfectly safely optimized to "x not in y", yet basic equality has to
have separate handling. The default handling does mean that you can
mostly ignore __ne__ and expect things to work, but if you subclass
something that has both, it'll break:

class Foo:
def __eq__(self, other):
print("Foo: %s == %s" % (self, other))
return True
def __ne__(self, other):
print("Foo: %s != %s" % (self, other))
return False

class Bar(Foo):
def __eq__(self, other):
print("Bar: %s == %s" % (self, other))
return False

>>> Bar() == 1
Bar: <__main__.Bar object at 0x7f40ebf3a128> == 1
False
>>> Bar() != 1
Foo: <__main__.Bar object at 0x7f40ebf3a128> != 1
False

Obviously this trivial example looks stupid, but imagine if the
equality check in the subclass USUALLY gives the same result as the
superclass, but differs in rare situations. Maybe you create a "bag"
class that functions a lot like collections.Counter but ignores zeroes
when comparing:

>>> class Bag(collections.Counter):
... def __eq__(self, other):
... # Disregard any zero entries when comparing to another Bag
... return {k:c for k,c in self.items() if c} == {k:c for k,c
in other.items() if c}
...
>>> b1 = Bag("aaabccdd")
>>> b2 = Bag("aaabccddq")
>>> b2["q"] -= 1
>>> b1 == b2
True
>>> b1 != b2
True
>>> dict(b1) == dict(b2)
False
>>> dict(b1) != dict(b2)
True

The behaviour of __eq__ is normal and sane. But since there's no
__ne__, the converse comparison falls back on dict.__ne__, not on
object.__ne__.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Thomas Jollans
On 07/01/18 20:55, Chris Angelico wrote:
> Under what circumstances would you want "x != y" to be different from
> "not (x == y)" ?

In numpy, __eq__ and __ne__ do not, in general, return bools.

Python 3.6.3 (default, Oct  3 2017, 21:45:48)
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> a = np.array([1,2,3,4])
>>> b = np.array([0,2,0,4])
>>> a == b
array([False,  True, False,  True], dtype=bool)
>>> a != b
array([ True, False,  True, False], dtype=bool)
>>> not (a == b)
Traceback (most recent call last):
  File "", line 1, in 
ValueError: The truth value of an array with more than one element is
ambiguous. Use a.any() or a.all()
>>> ~(a == b)
array([ True, False,  True, False], dtype=bool)
>>>

I couldn't tell you why this was originally allowed, but it does turn
out to be strangely useful. (As far as the numpy API is concerned, it
would be even nicer if 'not' could be overridden, IMHO)

Cheers
Thomas
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
Whoops, premature send. Picking up from the last paragraph.

This is good. This is correct. For inequalities, you can't assume that
>= is the exact opposite of < or the combination of < and == (for
example, sets don't behave like numbers, so "x <= y" is very different
from "x < y or x == y"). But the one that confuses me is != or __ne__.
If you don't create it, you get default behaviour:

>>> class Ham:
... def __eq__(self, other):
... print("%s equals %s" % (self, other))
... return True
...
>>> Ham() == 1
<__main__.Ham object at 0x7fb7557c0278> equals 1
True
>>> 2 == Ham()
<__main__.Ham object at 0x7fb7557c0278> equals 2
True
>>> Ham() != 3
<__main__.Ham object at 0x7fb7557c0278> equals 3
False
>>> 4 != Ham()
<__main__.Ham object at 0x7fb7557c0278> equals 4
False
>>> x = Ham()
>>> x == x
<__main__.Ham object at 0x7fb7557b80f0> equals <__main__.Ham object at
0x7fb7557b80f0>
True
>>> x != x
<__main__.Ham object at 0x7fb7557b80f0> equals <__main__.Ham object at
0x7fb7557b80f0>
False

Under what circumstances would you want "x != y" to be different from
"not (x == y)" ? How would this make for sane behaviour? Even when
other things go weird with equality checks, that basic parallel is
always maintained:

>>> z = float("nan")
>>> z == z
False
>>> z != z
True

Python gives us a "not in" operator that uses __contains__ and then
negates the result. There is no way for "x not in y" to be anything
different from "not (x in y)", as evidenced by the peephole optimizer:

>>> dis.dis("x not in y")
  1   0 LOAD_NAME0 (x)
  2 LOAD_NAME1 (y)
  4 COMPARE_OP   7 (not in)
  6 RETURN_VALUE
>>> dis.dis("not (x in y)")
  1   0 LOAD_NAME0 (x)
  2 LOAD_NAME1 (y)
  4 COMPARE_OP   7 (not in)
  6 RETURN_VALUE

So why isn't != done the same way? Is it historical?

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Why does __ne__ exist?

2018-01-07 Thread Chris Angelico
When you create a Python class, you can create dunder methods to
define how your objects respond to the standard operators. With
comparison operators, Python will happily switch the operands around
to find a method to call:

>>> class Spam():
... def __lt__(self, other):
... print("%s is less than %s" % (self, other))
... return True
...
>>> Spam() < 2
<__main__.Spam object at 0x7fb7557b1fd0> is less than 2
True
>>> 3 > Spam()
<__main__.Spam object at 0x7fb7557b1fd0> is less than 3
True
>>> 4 > Spam() < 5
<__main__.Spam object at 0x7fb7557b1fd0> is less than 4
<__main__.Spam object at 0x7fb7557b1fd0> is less than 5
True

But Python will not automatically assume the converse:

>>> Spam() >= 6
Traceback (most recent call last):
  File "", line 1, in 
TypeError: '>=' not supported between instances of 'Spam' and 'int'
>>> Spam() > 7
Traceback (most recent call last):
  File "", line 1, in 
TypeError: '>' not supported between instances of 'Spam' and 'int'

This is good. This is correct. For inequalities, you can't assume that
>= is the exact opposite of < (for example, sets don't behave like
numbers, so "x <= y" is very different from )
-- 
https://mail.python.org/mailman/listinfo/python-list