Try to make sense of this table: https://gist.github.com/ramalho/9f67fd245f424939c73e5c3bb21fa949
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.
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:
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)
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg