2

I recently found out that python has a special value NotImpemented to be used with respect to binary special methods to indicate that some operation has not been implemented.

The peculiar about this is that when checked in a binary situation it is always equivalent to True.

For example using io.BytesIO (which is a case where __eq__ in not implemented for example) for two objects in comparison will virtually return True. As in this example (encoded_jpg_io1 and encoded_jpg_io2 are objects of the io.BytesIO class):

if encoded_jpg_io1.__ne__(encoded_jpg_io2):
    print('Equal')
else:
    print('Unequal')

Equal

if encoded_jpg_io1.__eq__(encoded_jpg_io2) == True:
    print('Equal')
else:
    print('Unequal')

Unequal

Since the second style is a bit too verbose and normally not prefered (even my pyCharm suggests to remove the explicit comparison with True) isn't a bit tricky behavior? I wouldn't have noticed it if I haven't explicitly print the result of the Boolean operation (which is not Boolean in this case at all).

I guess suggesting to be considered False would cause the same problem with __ne__ so we arew back to step one.

So, the only way to check out for these cases is by doing an exact comparison with True or False in the opposite case.

I know that NotImpemented is preferred over NotImplementedError for various reasons so I am not asking for any explanation over why this matter.

SitiSchu
  • 1,361
  • 11
  • 20
Eypros
  • 5,370
  • 6
  • 42
  • 75
  • `NotImplemented` is designed to give the correct results when you use `a==b`, not `a.__eq__(b)`. If you opt to call the `__eq__` and `__ne__` directly then you have to handle with the added complication of `NotImplemented` yourself. – khelwood Mar 28 '18 at 12:10
  • So if you were to rephrase this into an actual question, it might be "Why does Python say that these objects are not equal (`!=`) instead of raising an exception?" – Josh Lee Mar 28 '18 at 12:27

1 Answers1

2

Per convention, objects that do not define a __bool__ method are considered truthy. From the docs:

By default, an object is considered true unless its class defines either a __bool__() method that returns False or a __len__() method that returns zero

This means that most classes, functions, and other builtin singletons are considered true, since they don't go out of their way to specify different behavior. (An exception is None, which is one of the few built-in singletons that does specifically signal it should be considered false):

>>> bool(int)  # the class, not an integer object
True
>>> bool(min)
True
>>> bool(object())
True
>>> bool(...)  # that's the Ellipsis object
True
>>> bool(NotImplemented)
True

There is no real reason for the NotImplemented object to break this convention. The problem with your code isn't that NotImplemented is considered truthy; the real problem is that x.__eq__(y) is not equivalent to x == y.

If you want to compare two objects for equality, doing it with x.__eq__(y) is incorrect. Using x.__eq__(y) == True instead is still incorrect.

The correct solution is to do comparison with the == operator. If, for whatever reason, you can't use the == operator directly, you should use the operator.eq function instead.

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • @user2357112 Sorry, but I'm not a fan of that edit. That's a bit too technical; I don't want venture that deep into python's inner workings. I think it's enough (and not incorrect) to say that *"`None` is explicitly defined to be falsy"*, without going into details about how that's implemented. Whether it has a `__bool__` method or not is really besides the point. I'm rolling it back. – Aran-Fey Mar 28 '18 at 18:06
  • Then why bring up the None special case at all? It has no externally visible effect now that `None` has a `__bool__` method, and `None` isn't even the only special-cased object; [`True` and `False` are also special-cased](https://github.com/python/cpython/blob/v3.6.4/Objects/object.c#L1225). – user2357112 Mar 28 '18 at 18:08
  • @user2357112 That's all specifically about CPython's implementation though. The point I'm trying to make is that `None` is well-defined to be falsy in the python language because it's commonly used in a boolean context, whereas other objects like `NotImplemented` are _not_ meant or designed to be used in a boolean context and therefore default to the standard behavior of being truthy. (So yes, `True` and `False` are also exceptions, but I don't think it's necessary to mention that booleans have a well-defined truth value...) – Aran-Fey Mar 28 '18 at 18:12
  • None is defined to be false, but not in a way that makes it an exception from the general rules. Your answer acts as if it's an exception. – user2357112 Mar 28 '18 at 18:17
  • @user2357112 Again, it's not about _how_ `None` gets its truth value. I'm arguing on a higher level here; it's about language semantics. The simple fact that the truth value of `None` is _explicitly_ stated in the documentation makes it different from `NotImplemented` or the ellipsis object or `int` or `min`. – Aran-Fey Mar 28 '18 at 18:21
  • I'd remove the mention of `None` from the answer, but I'm sure someone would well-actually me in the comments if I did... :p – Aran-Fey Mar 28 '18 at 18:23