The following code throws a mypy error:
from typing import Dict, Any
def asd(x: Dict[str, Any]) -> bool:
return x['a'] == 1
asd({"x": 2})
IMO it doesn't matter what is passed as a dict. x['a'] == 1
should always be a boolean. But mypy complains with:
test.py:4: error: Returning Any from function declared to return "bool" [no-any-return]
Any Idea how to fix is besides peppering our codebase with # type: ignore[no-any-return]
?
my setup.cfg for reference:
namespace_packages = True
explicit_package_bases = True
check_untyped_defs = True
warn_return_any = True
warn_unused_ignores = True
show_error_codes = True
The __eq__
method, one of Python's rich comparison methods, does not always return True
or False
, it can also return NotImplemented
(or other values).
This is used, for example, in the case where the first type cannot compare itself with the second, so a.__eq__(b)
returns NotImplemented
. In that case, it then tries b.__eq__(a)
as a fallback (a). If that also returns NotImplemented
, then I believe it falls back to the base object
type to figure out equality.
You can see the way mypy
has defined the typing for this in its source code, specifically mypy/mypy/typeshed/stdlib/_operator.pyi
:
def eq(__a: object, __b: object) -> Any: ...
That's why you're getting that particular message.
As a quick fix, you can change the function to return bool(x['x'] == 1)
. This is exactly what Python itself does when evaluating the return value in a boolean context, so should be acceptable for placating mypy
(b).
(a) It's a little more complex than that, involving also the hierarchical type relationships of the two items, but the bottom line still stands: rich comparison operators do not always return a bool
.
(b) This is covered in the Python documentation for the data model (my emphasis):
A rich comparison method may return the singleton
NotImplemented
if it does not implement the operation for a given pair of arguments. By convention,False
andTrue
are returned for a successful comparison. However, these methods can return any value, so if the comparison operator is used in a Boolean context (e.g., in the condition of anif
statement), Python will callbool()
on the value to determine if the result is true or false.
Interestingly, though this doesn't change the validity of this answer, the section after that states:
By default,
object
implements__eq__()
by usingis
, returningNotImplemented
in the case of a false comparison:True if x is y else NotImplemented
.
But I'm not convinced that's accurate. In the actual code, both ==
and !=
seem to result in True
or False
, depending on object identity, and never returning NotImplemented
or raising an exception (the exceptions happen only for ordering comparisons like <
or >=
):
/* If neither object implements it, provide a sensible default
for == and !=, but raise an exception for ordering. */
switch (op) {
case Py_EQ:
res = (v == w) ? Py_True : Py_False;
break;
case Py_NE:
res = (v != w) ? Py_True : Py_False;
break;
default:
_PyErr_Format(tstate, PyExc_TypeError, ...
As pointed out in a comment, the identity checking is done at a different level to base objects, for example,the type objects. So I'd take that part of the documentation with a grain of salt :-)
In any case, it shows that, while base objects may only give you back true or false, some derived objects can also give other values, which is why the warning you're seeing is correct.