On Thu, Jun 18, 2020 at 8:45 AM Luciano Ramalho <luciano@ramalho.org> wrote:
On Wed, Jun 17, 2020 at 9:31 PM Guido van Rossum <guido@python.org> wrote:
> Could you please post a corrected version of the script?

The script and table are correct and were correct when I posted them.

Only parts of my description in my first e-mail had some errors when I
wrongly reproduced some calls to issubclass().

I can't fix a message already sent, so I will write a blog post
describing the problems more accurately. I apologize for the
confusion.

Ah, sorry. Somehow Alex Martelli's post started a new thread in GMail and I didn't follow exactly what was going on; then I misread your reply as indicating that the script and table were wrong. My apologies!
 
> Also, IMO the right way to test at runtime is still the numeric tower. The SupportsXxx classes are a crutch for static type checkers and their operational definition is "does the object have a __xxx__ method".

I understand that. The problem is that they are misleading, as the table shows.

Here an attempt to use typing.SupportsFloat:

>>> c = 1+0j
>>> type(c)
<class 'complex'>
>>> isinstance(c, typing.SupportsFloat)
True
>>> float(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't convert complex to float
>>> sys.version_info
sys.version_info(major=3, minor=8, micro=0, releaselevel='final', serial=0)

The opposite problem also occurs—a protocol says "no" but reality says "yes":

>>> x = 1.1
>>> isinstance(x, typing.SupportsComplex)
False
>>> complex(x)
(1.1+0j)

Thanks for your attention.

And thanks for the explanation -- your complaint is now much clearer.

At *runtime* SupportsComplex and SupportsFloat and the others are predicated on "does it have a __complex__ / __float__ / etc. attribute." And float has no __complex__ method, but complex has a __float__ method, as well as an __int__ attribute -- both appear to be intended to give better error messages. However the runtime introspection implemented by typing's @runtime_checkable cannot introspect the true nature of these methods -- it just checks for their presence and returns True or False based on that. That's why SupportsFloat is a bad way to check for whether something "is" a float or even floatable. (Note that some strings are also floatable -- but SupportsFloat doesn't know.)

As for why complex(x) works even though there's no x.__float__, that's because the complex() constructor accepts one or two floats as arguments and treats them as the real and imaginary parts.

So I stick to my observation that the numeric tower is still the best way to check for a specific type of number, since all builtin types are registered as member of the appropriate level in the tower.
 
What a type checker does is potentially different -- and Teddy Sudol's observation is correct that typeshed should be adjusted to set the return type of complex.__float__ and complex.__int__ to NoReturn. Somebody should probably send a PR their way.

--
--Guido van Rossum (python.org/~guido)