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
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/3YGNHGWZOU5AIBS3A52CAHPJJLY7J2CS/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to