Here's the short version:
* "Convert float-like object to a float" is a useful operation * For a C extension, there's an obvious way to spell that operation: use `PyFloat_AsDouble` * In pure Python, there's no Obvious Way To Do It * Proposal: expose the equivalent of `PyFloat_FromDouble(PyFloat_AsDouble(obj))` to Python * Question: where should it go?
Longer version:
Various parts of CPython that expect a float will accept anything "float-like"; examples include most of the `math` module functions, the `struct` module, `ctypes`, `statistics`, old-style string formatting with `"%f"` and the like, anything that uses `PyArg_ParseTuple` with the `'d'` format converter, anything that uses argument clinic and declares an input as `double`, and more. This is a Good Thing: it means that as a Python user, you can make your own types compatible with these operations simply by giving them a `__float__` method.
Now suppose that as a Python user and 3rd party library writer, I want to write my own function that similarly accepts anything float-like, following the same rules that core Python uses. If I'm writing a C extension module, this is trivially easy: I use `PyFloat_FromDouble`, which is part of the stable ABI. However, if I'm writing in pure Python, it's surprisingly awkward to clearly express the equivalent behaviour, and I'd like to have an obvious way to do it.
Some of the non-obvious ways:
1. Use the `float` constructor. This _ought_ to be the one obvious way to do it: it'll certainly accept anything float-like and convert it to a float. But it will also accept instances of `str`, `bytes`, `bytearray`, and anything supporting the buffer protocol, so if you want to exclude those (for example, because passing a non-number to your numeric code is likely to be a coding error, and you'd like to catch it as such) you need to do a LBYL check.
2. Call the object's `__float__` method. But this is fraught with peril, too: for a proper equivalent, you need to be careful to look up `__float__` on the type, not the object itself. And then a new version of Python changes `PyFloat_AsDouble` to also accept objects with `__index__`, and suddenly your version no longer matches what Python does. (This happened.)
3. Leverage the `math` module's existing ability to do this. For example, I can do `x_as_float = math.copysign(x, x)`. This works, giving me _exactly_ the semantics that I want. But I think it would be stretch to call this an *obvious* way to do it.
4. Write your own small C extension containing a single function that looks like `return PyFloat_FromDouble(PyFloat_AsDouble(obj))` (with extra error checking, fast path for exact floats, etc.).
So my modest proposal is: expose the conversion represented by `PyFloat_AsDouble` to Python somewhere: either on the `float` type itself, or somewhere in the standard library. My question for everyone on this list is: _if_ it were to be added, where should it go? (There's also the "what should it be called" question, of course.)
I have a proof-of-concept PR [1] that exposes this in the `operator` module as `operator.as_float`. See also the discussion on the tracker [2].
[1] https://github.com/python/cpython/pull/20481 [2] https://bugs.python.org/issue40801