Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add operator.as_float
  • Loading branch information
mdickinson committed May 28, 2020
commit 3fb74136abfd9252827589b20338692354a2de3b
7 changes: 7 additions & 0 deletions Doc/library/operator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ The mathematical and bitwise operations are the most numerous:
Return the bitwise and of *a* and *b*.


.. function:: as_float(a)

Return *a* converted to an float. Equivalent to ``float(a)``, except
Comment thread
mdickinson marked this conversation as resolved.
Outdated
that conversion from a string or bytestring is not permitted. The result
always has exact type :class:`float`.


.. function:: floordiv(a, b)
__floordiv__(a, b)

Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ New Modules
Improved Modules
================

operator
--------

Added :func:`operator.as_float` to convert a numeric object to :class:`float`.
This exposes to Python a conversion whose semantics exactly match Python's
own implicit float conversions, for example as used in the :mod:`math` module.

tracemalloc
-----------

Expand Down
10 changes: 10 additions & 0 deletions Lib/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ def and_(a, b):
"Same as a & b."
return a & b

def as_float(obj):
"""
Convert something numeric to float.

Same as float(obj), but does not accept strings.
"""
if isinstance(obj, (str, bytes, bytearray)):
Comment thread
mdickinson marked this conversation as resolved.
Outdated
raise TypeError("as_float argument must be numeric")
Comment thread
mdickinson marked this conversation as resolved.
Outdated
return float(obj)

def floordiv(a, b):
"Same as a // b."
return a // b
Expand Down
76 changes: 76 additions & 0 deletions Lib/test/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,75 @@ def __length_hint__(self):
with self.assertRaises(LookupError):
operator.length_hint(X(LookupError))

def test_as_float(self):
operator = self.module

# Exact type float.
self.assertIsFloatWithValue(operator.as_float(2.3), 2.3)

# Subclass of float.
class MyFloat(float):
pass

self.assertIsFloatWithValue(operator.as_float(MyFloat(-1.56)), -1.56)

# Non-float with a __float__ method.
class FloatLike:
def __float__(self):
return 1729.0

self.assertIsFloatWithValue(operator.as_float(FloatLike()), 1729.0)

# Non-float with a __float__ method that returns an instance
# of a subclass of float.
class FloatLike2:
def __float__(self):
return MyFloat(918.0)

self.assertIsFloatWithValue(operator.as_float(FloatLike2()), 918.0)

# Plain old integer
self.assertIsFloatWithValue(operator.as_float(2), 2.0)

# Integer subclass.
class MyInt(int):
pass

self.assertIsFloatWithValue(operator.as_float(MyInt(-3)), -3.0)

# Object supplying __index__ but not __float__.
class IntegerLike:
def __index__(self):
return 77

self.assertIsFloatWithValue(operator.as_float(IntegerLike()), 77.0)

# Same as above, but with __index__ returning an instance of an
# int subclass.
class IntegerLike2:
def __index__(self):
return MyInt(78)

self.assertIsFloatWithValue(operator.as_float(IntegerLike2()), 78.0)

# Object with both __float__ and __index__; __float__ should take
# precedence.
class Confused:
def __float__(self):
return 123.456

def __index__(self):
return 123

self.assertIsFloatWithValue(operator.as_float(Confused()), 123.456)

# Not convertible.
bad_values = ["123", b"123", bytearray(b"123"), None, 1j]
for bad_value in bad_values:
with self.subTest(bad_value=bad_value):
with self.assertRaises(TypeError):
operator.as_float(bad_value)

def test_dunder_is_original(self):
operator = self.module

Expand All @@ -509,6 +578,13 @@ def test_dunder_is_original(self):
if dunder:
self.assertIs(dunder, orig)

def assertIsFloatWithValue(self, actual, expected):
self.assertIs(type(actual), float)
# Compare reprs rather than values, to deal correctly with corner
# cases like nans and signed zeros.
self.assertEqual(repr(actual), repr(expected))


class PyOperatorTestCase(OperatorTestCase, unittest.TestCase):
module = py_operator

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Add a new :func:`operator.as_float` function for converting an arbitrary
Python numeric object to :class:`float`. This is a simple wrapper around the
:c:func:`PyFloat_AsDouble` C-API function. The intent is to provide at
Python level a conversion whose semantics exactly match Python's own
implicit float conversions, for example those used in the :mod:`math`
module.
45 changes: 45 additions & 0 deletions Modules/_operator.c
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,50 @@ _tscmp(const unsigned char *a, const unsigned char *b,
return (result == 0);
}

/*[clinic input]
_operator.as_float ->

obj: object
/

Return *obj* interpreted as a float.

If *obj* is already of exact type float, return it unchanged.

If *obj* is already an instance of float (including possibly an instance of a
float subclass), return a float with the same value as *obj*.

If *obj* is not an instance of float but its type has a __float__ method, use
that method to convert *obj* to a float.

If *obj* is not an instance of float and its type does not have a __float__
method but does have an __index__ method, use that method to
convert *obj* to an integer, and then convert that integer to a float.

If *obj* cannot be converted to a float, raise TypeError.

Calling as_float is equivalent to calling *float* directly, except that string
objects are not accepted.

[clinic start generated code]*/

static PyObject *
_operator_as_float(PyObject *module, PyObject *obj)
/*[clinic end generated code: output=25b27903bbd14913 input=39db64b91327f393]*/
{
if (PyFloat_CheckExact(obj)) {
Py_INCREF(obj);
return obj;
}

double x = PyFloat_AsDouble(obj);
if (x == -1.0 && PyErr_Occurred()) {
return NULL;
}
return PyFloat_FromDouble(x);
}


/*[clinic input]
_operator.length_hint -> Py_ssize_t

Expand Down Expand Up @@ -929,6 +973,7 @@ static struct PyMethodDef operator_methods[] = {
_OPERATOR_GE_METHODDEF
_OPERATOR__COMPARE_DIGEST_METHODDEF
_OPERATOR_LENGTH_HINT_METHODDEF
_OPERATOR_AS_FLOAT_METHODDEF
{NULL, NULL} /* sentinel */

};
Expand Down
28 changes: 27 additions & 1 deletion Modules/clinic/_operator.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.