Skip to content

Commit d15d039

Browse files
authored
optimize Python float validation (#826)
1 parent 4ec40e0 commit d15d039

File tree

3 files changed

+25
-18
lines changed

3 files changed

+25
-18
lines changed

src/input/input_python.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,21 +316,22 @@ impl<'a> Input<'a> for PyAny {
316316
fn ultra_strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
317317
if self.is_instance_of::<PyInt>() {
318318
Err(ValError::new(ErrorType::FloatType, self))
319-
} else if self.is_instance_of::<PyFloat>() {
320-
Ok(EitherFloat::Py(self))
319+
} else if let Ok(float) = self.downcast::<PyFloat>() {
320+
Ok(EitherFloat::Py(float))
321321
} else {
322322
Err(ValError::new(ErrorType::FloatType, self))
323323
}
324324
}
325325
fn strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
326326
if PyFloat::is_exact_type_of(self) {
327-
Ok(EitherFloat::Py(self))
327+
// Safety: self is PyFloat
328+
Ok(EitherFloat::Py(unsafe { self.downcast_unchecked::<PyFloat>() }))
328329
} else if let Ok(float) = self.extract::<f64>() {
329330
// bools are cast to floats as either 0.0 or 1.0, so check for bool type in this specific case
330331
if (float == 0.0 || float == 1.0) && PyBool::is_exact_type_of(self) {
331332
Err(ValError::new(ErrorType::FloatType, self))
332333
} else {
333-
Ok(EitherFloat::Py(self))
334+
Ok(EitherFloat::F64(float))
334335
}
335336
} else {
336337
Err(ValError::new(ErrorType::FloatType, self))
@@ -339,7 +340,8 @@ impl<'a> Input<'a> for PyAny {
339340

340341
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
341342
if PyFloat::is_exact_type_of(self) {
342-
Ok(EitherFloat::Py(self))
343+
// Safety: self is PyFloat
344+
Ok(EitherFloat::Py(unsafe { self.downcast_unchecked::<PyFloat>() }))
343345
} else if let Some(cow_str) = maybe_as_string(self, ErrorType::FloatParsing)? {
344346
match cow_str.as_ref().parse::<f64>() {
345347
Ok(i) => Ok(EitherFloat::F64(i)),

src/input/return_enums.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use pyo3::exceptions::PyTypeError;
1010
use pyo3::prelude::*;
1111
use pyo3::types::iter::PyDictIterator;
1212
use pyo3::types::{
13-
PyByteArray, PyBytes, PyDict, PyFrozenSet, PyIterator, PyList, PyMapping, PySequence, PySet, PyString, PyTuple,
13+
PyByteArray, PyBytes, PyDict, PyFloat, PyFrozenSet, PyIterator, PyList, PyMapping, PySequence, PySet, PyString,
14+
PyTuple,
1415
};
1516
use pyo3::{ffi, intern, AsPyPointer, PyNativeType};
1617

@@ -910,18 +911,23 @@ impl<'a> IntoPy<PyObject> for EitherInt<'a> {
910911
}
911912

912913
#[cfg_attr(debug_assertions, derive(Debug))]
914+
#[derive(Copy, Clone)]
913915
pub enum EitherFloat<'a> {
914916
F64(f64),
915-
Py(&'a PyAny),
917+
Py(&'a PyFloat),
916918
}
917919

918-
impl<'a> TryInto<f64> for EitherFloat<'a> {
919-
type Error = ValError<'a>;
920-
921-
fn try_into(self) -> ValResult<'a, f64> {
920+
impl<'a> EitherFloat<'a> {
921+
pub fn as_f64(self) -> f64 {
922922
match self {
923-
EitherFloat::F64(f) => Ok(f),
924-
EitherFloat::Py(i) => i.extract().map_err(|_| ValError::new(ErrorType::FloatParsing, i)),
923+
EitherFloat::F64(f) => f,
924+
925+
EitherFloat::Py(f) => {
926+
{
927+
// Safety: known to be a python float
928+
unsafe { ffi::PyFloat_AS_DOUBLE(f.as_ptr()) }
929+
}
930+
}
925931
}
926932
}
927933
}

src/validators/float.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,10 @@ impl Validator for FloatValidator {
7272
_recursion_guard: &'s mut RecursionGuard,
7373
) -> ValResult<'data, PyObject> {
7474
let either_float = input.validate_float(extra.strict.unwrap_or(self.strict), extra.ultra_strict)?;
75-
let float: f64 = either_float.try_into()?;
76-
if !self.allow_inf_nan && !float.is_finite() {
75+
if !self.allow_inf_nan && !either_float.as_f64().is_finite() {
7776
return Err(ValError::new(ErrorType::FiniteNumber, input));
7877
}
79-
Ok(float.into_py(py))
78+
Ok(either_float.into_py(py))
8079
}
8180

8281
fn different_strict_behavior(
@@ -119,7 +118,7 @@ impl Validator for ConstrainedFloatValidator {
119118
_recursion_guard: &'s mut RecursionGuard,
120119
) -> ValResult<'data, PyObject> {
121120
let either_float = input.validate_float(extra.strict.unwrap_or(self.strict), extra.ultra_strict)?;
122-
let float: f64 = either_float.try_into()?;
121+
let float: f64 = either_float.as_f64();
123122
if !self.allow_inf_nan && !float.is_finite() {
124123
return Err(ValError::new(ErrorType::FiniteNumber, input));
125124
}
@@ -155,7 +154,7 @@ impl Validator for ConstrainedFloatValidator {
155154
return Err(ValError::new(ErrorType::GreaterThan { gt: gt.into() }, input));
156155
}
157156
}
158-
Ok(float.into_py(py))
157+
Ok(either_float.into_py(py))
159158
}
160159

161160
fn different_strict_behavior(

0 commit comments

Comments
 (0)