complex doesn't actually implement SupportsFloat or SupportsInt though, at least conceptually.

SupportsFloat is essentially:
```
class SupportsFloat(Protocol):
  def __float__(self) -> float: ...
```

Meanwhile, complex is:
```
class complex:
  def __float__(self) -> NoReturn: ...
```
(complex.__float__ should be annotated with NoReturn because it unconditionally raises an exception.)

"A concrete type X is a subtype of protocol P if and only if X implements all protocol members of P with compatible types." Since NoReturn is not compatible with float, complex isn't a subtype (doesn't implement) SupportsFloat.
In fact, complex unsafely overlaps SupportsFloat, and `isinstance(1j, SupportsFloat)` should be rejected by type checkers per PEP 544.
All of this holds true for SupportsInt as well, because complex.__int__ also always raises an exception.

-- Teddy


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.

> 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.

Best,

Luciano




> The best way to discover at runtime whether something's an integer is still the numeric tower. (With one big caveat: I don't know whether numpy supports this. Last I looked there was scant mention of the numeric tower in the numpy docs, but IIRC there is code in numpy that uses it.)
>
> On Tue, Jun 16, 2020 at 8:18 PM Luciano Ramalho <luciano@ramalho.org> wrote:
>>
>> TL;DR
>>
>> Try to make sense of this table:
>> https://gist.github.com/ramalho/9f67fd245f424939c73e5c3bb21fa949
>>
>>
>> CONTEXT
>>
>> PEP 484 rejects the numeric tower (number.Number, number.Complex,
>> number.Real etc.).
>>
>> The typing module now offers number-related SupportsX protocols which
>> are runtime checkable, so I assumed some of these protocols could
>> replace the numeric tower in practice.
>>
>> This is now more important than before, given the widespread use of
>> NumPy with its dozens of numeric types.
>>
>>
>> QUESTIONS
>>
>> What is the current best practice for testing numeric types at
>> runtime, if the numeric tower is problematic?
>>
>> What use cases prompted the inclusion of the number-related SupportsX
>> protocols as runtime checked ABCs?
>>
>>
>> ISSUES WITH PROTOCOLS
>>
>> I wish I could forget about the numeric tower and use the SuportsX
>> protocols, but I don't understand some of the results I'm getting with
>> these protocols:
>>
>> 1) SupportsFloat
>> issubclass(complex, typing.SupportsFloat) returns True
>> but float(1+2j) raises TypeError: can't convert complex to float
>> (the complex class does implement __float__, but I get that TypeError)
>>
>> 2) SupportsInt
>> Same issue above: issubclass(complex, typing.SupportsInt) is True but
>> int(1+2j) raises TypeError.
>> In addition, issubclass(fractions.Fraction, typing.SupportsInt)
>> returns False, but int(Fraction(7, 2) works, returns 3.
>>
>> 3) SupportsComplex
>> Is issubclass(NT, typing.SupportsInt) is true ONLY for NT in
>> [numpy.complex64, Decimal, and Fraction] but in fact all the numeric
>> types from the stdlib and NumPy that I tried can be passed to
>> complex() with no errors (as the first argument).
>>
>>
>> THE TABLE AND SCRIPT TO BUILD IT
>>
>> I wrote a little script to create a table that shows these issues. See
>> the table and script here if you are interested:
>>
>> https://gist.github.com/ramalho/9f67fd245f424939c73e5c3bb21fa949
>>
>> The columns are concrete numeric types from the Python stdlib and NumPy.
>>
>> The rows represent three kinds of tests:
>>
>> 1) issubclass results against numbers ABCs
>>     Example: issubclass(number.Real, numpy.float16)
>> 2) issubclass results against typing protocols
>>     Example: issubclass(typing.SupportsFloat, numpy.float16)
>> 3) application of a built-in to a value built from a concrete type,
>> given argument 1
>>     Example 1: complex(float(1)) # result: (1+0j) with ComplexWarning
>>     Example 2: float(complex(1))  # no result, TypeError: can't
>> convert complex to float
>>     Example 3: round(numpy.complex64(1))  # result: (1+0j)
>>
>> Cheers,
>>
>> Luciano
>>
>>
>> --
>> Luciano Ramalho
>> |  Author of Fluent Python (O'Reilly, 2015)
>> |     http://shop.oreilly.com/product/0636920032519.do
>> |  Technical Principal at ThoughtWorks
>> |  Twitter: @ramalhoorg
>> _______________________________________________
>> Typing-sig mailing list -- typing-sig@python.org
>> To unsubscribe send an email to typing-sig-leave@python.org
>> https://mail.python.org/mailman3/lists/typing-sig.python.org/
>> Member address: guido@python.org
>
>
>
> --
> --Guido van Rossum (python.org/~guido)
> Pronouns: he/him (why is my pronoun here?)



--
Luciano Ramalho
|  Author of Fluent Python (O'Reilly, 2015)
|     http://shop.oreilly.com/product/0636920032519.do
|  Technical Principal at ThoughtWorks
|  Twitter: @ramalhoorg
_______________________________________________
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
https://mail.python.org/mailman3/lists/typing-sig.python.org/
Member address: tsudol@google.com