There might not be a sensible, unambiguously correct conversion
The popular Numpy third-party library for Python provides a clear case study for this possibility. Because Python is dynamically typed, it is forced to accept code that would invoke an implicit conversion. Early on, the language implementers chose to allow such conversions to be attempted, and specified that types could define this conversion using the __bool__
"magic method".
This is a trade-off made for pragmatic reasons; in many other places, Python refuses implicit conversions and remains fairly strongly typed (for example, it does not allow concatenating an integer to a string with the semantics of implicitly using a string representation of the integer). In the case of conditional logic, the Python implementers apparently felt that requiring conversions all the time to avoid exceptions would be too tedious. However, the Numpy implementers found a need to refuse this convenience.
Numpy defines an array type that provides a bunch of operator overloads that implicitly "broadcast" operations - comparing two identically-shaped arrays element-wise, or repeating the comparison along one or more axes if the array shapes are "compatible".
In particular, for two arrays a
and b
of the same shape, the ==
operator (via overloaded __eq__
method) will compare each pair of corresponding elements, and return a new array of booleans of the same shape with the comparison results in the corresponding places:
>>> import numpy as np
>>> a = np.ones((3,3))
>>> b = np.ones((3,3))
>>> a[:,1] = 0
>>> b[1,:] = 0
>>> a
array([[1., 0., 1.],
[1., 0., 1.],
[1., 0., 1.]])
>>> b
array([[1., 1., 1.],
[0., 0., 0.],
[1., 1., 1.]])
>>> a == b
array([[ True, False, True],
[False, True, False],
[ True, False, True]])
This sort of functionality is considered very useful, and a huge part of the reason why Python enjoys the popularity it does today with data scientists.
So the next question is, what should we expect to happen for if a == b:
, or in a conditional expression like 'y' if a == b else 'n'
?
Python has built-in fallback logic for comparisons - normally, everything that isn't explicitly falsy via an overloaded __bool__
, is truthy (via object.__bool__
). However, Numpy explicitly overloads __bool__
for arrays to raise an exception unless the array has exactly one element. (It used to consider zero-element arrays falsy, but current versions also raise an exception here.)
Why?
This is in accordance with the Zen of Python: "In the face of ambiguity, refuse the temptation to guess".
Think about it: if a == b:
comparing two Numpy arrays, presumably, probably meant to check whether every corresponding pair of elements is equal. But the result of a == b
is just an array of boolean values. Meanwhile, if c:
, where c
was an already existing array of boolean values, probably means to check whether any element is True
. There is no way for the __bool__
operator overload to know which was meant, because it does not have access to the AST.
Moreover, such logic is often wrong-headed anyway. Commonly, people want the code inside the block to describe work to do with a single pair of elements, and the code author wants that calculation to be broadcast - but Numpy simply cannot work this way. There definitely isn't any operator to overload, because if
is a statement rather than an expression. But even the conditional expression form doesn't work, and Numpy couldn't make it work if they wanted to. One might think that 'y' if a == b else 'n'
ought to produce an array of strings, but even if it could work, this would be deeply magical and many Pythonistas would complain about it being complex and surprising. (Even using the x if y else z
conditional expression is not universally accepted; but it would be especially hard to accept that the result might be neither x
nor z
!)
Instead, that job requires a different tool, such as numpy.where
, or using the a == b
result as a mask for another array, etc.
See also the corresponding canonical on Stack Overflow about the error message generated by the attempted implicit conversion - especially my answer there.
if (something() == ERROR_SUCCESS)
instead ofif (!something())
$\endgroup$