Replace FromPyObject with the more powerful ExtractPyObject.

This is a [breaking-change] and makes the trait more difficult to implement.
The usage through PyObject::extract() is unchanged.

This change allows extracting a `&str` through a temporary `Cow<str>`
without having to copy the string data from python to rust
(at least in cases where the python string is UTF-8 encoded).

This is preparation in hope I'll be able to make py_fn!()
automatically extract the function arguments.
This commit is contained in:
Daniel Grunwald 2015-07-18 22:39:55 +02:00
parent 1ad27b977e
commit 45d03bf8bb
9 changed files with 181 additions and 100 deletions

View File

@ -71,14 +71,45 @@ pub trait ToPyObject<'p> {
}
/// FromPyObject is implemented by various types that can be extracted from a Python object.
pub trait FromPyObject<'p> {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, Self>;
///
/// Usage:
/// ```let obj: PyObject = ...;
/// let prepared = <TargetType as FromPyObject>::prepare_extract(&obj);
/// let extracted = try!(extract(&prepared));```
///
/// Note: depending on the implementation, the lifetime of the extracted result may
/// depend on the lifetime of the `obj` or the `prepared` variable.
///
/// For example, when extracting `&str` from a python byte string, the resulting string slice will
/// point to the existing string data (lifetime: `'source`).
/// On the other hand, when extracting `&str` from a python unicode string, the preparation step
/// will convert the string to UTF-8, and the resulting string slice will have lifetime `'prepared`.
/// Since only which of these cases applies depends on the runtime type of the python object,
/// both the `obj` and `prepared` variables must outlive the resulting string slice.
///
/// In cases where the result does not depend on the `'prepared` lifetime,
/// the inherent method `PyObject::extract()` can be used.
pub trait ExtractPyObject<'python, 'source, 'prepared> {
type Prepared;
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared>;
fn extract(prepared: &'prepared Self::Prepared) -> PyResult<'python, Self>;
}
impl <'p, T> FromPyObject<'p> for T where T: PythonObjectWithCheckedDowncast<'p> {
impl <'python, 'source, 'prepared, T> ExtractPyObject<'python, 'source, 'prepared>
for T where T: PythonObjectWithCheckedDowncast<'python> {
type Prepared = &'source PyObject<'python>;
#[inline]
fn from_py_object(s : &PyObject<'p>) -> PyResult<'p, T> {
Ok(try!(s.clone().cast_into()))
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(obj)
}
#[inline]
fn extract(&&ref obj: &'prepared Self::Prepared) -> PyResult<'python, T> {
Ok(try!(obj.clone().cast_into()))
}
}

View File

@ -88,7 +88,7 @@ pub use err::{PyErr, PyResult};
pub use objects::*;
pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject};
pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python};
pub use conversion::{FromPyObject, ToPyObject};
pub use conversion::{ExtractPyObject, ToPyObject};
pub use objectprotocol::{ObjectProtocol};
pub use rustobject::{PyRustType, PyRustObject};
pub use rustobject::typebuilder::PyRustTypeBuilder;

View File

@ -2,7 +2,7 @@ use ffi;
use python::{Python, ToPythonPointer};
use err::PyResult;
use super::PyObject;
use conversion::{FromPyObject, ToPyObject};
use conversion::{ExtractPyObject, ToPyObject};
/// Represents a Python `bool`.
pub struct PyBool<'p>(PyObject<'p>);
@ -44,9 +44,7 @@ impl <'p> ToPyObject<'p> for bool {
/// Converts a Python `bool` to a rust `bool`.
///
/// Fails with `TypeError` if the input is not a Python `bool`.
impl <'p> FromPyObject<'p> for bool {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, bool> {
Ok(try!(s.clone().cast_into::<PyBool>()).is_true())
}
}
extract!(obj to bool => {
Ok(try!(obj.cast_as::<PyBool>()).is_true())
});

View File

@ -20,7 +20,7 @@ use python::{Python, PythonObject, ToPythonPointer};
use err::{self, PyResult};
use super::object::PyObject;
use ffi::{self, Py_ssize_t};
use conversion::{ToPyObject, FromPyObject};
use conversion::{ToPyObject, ExtractPyObject};
/// Represents a Python `list`.
pub struct PyList<'p>(PyObject<'p>);
@ -136,9 +136,19 @@ impl <'p, T> ToPyObject<'p> for [T] where T: ToPyObject<'p> {
}
}
impl <'p, T> FromPyObject<'p> for Vec<T> where T: FromPyObject<'p> {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, Vec<T>> {
let list = try!(s.cast_as::<PyList>());
impl <'python, 'source, 'prepared, T> ExtractPyObject<'python, 'source, 'prepared>
for Vec<T> where T: for<'s, 'p> ExtractPyObject<'python, 's, 'p> {
type Prepared = &'source PyObject<'python>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(obj)
}
#[inline]
fn extract(&&ref obj: &'prepared Self::Prepared) -> PyResult<'python, Vec<T>> {
let list = try!(obj.cast_as::<PyList>());
let mut v = Vec::with_capacity(list.len());
for i in 0 .. list.len() {
v.push(try!(list.get_item(i).extract::<T>()));

View File

@ -151,6 +151,27 @@ macro_rules! pyobject_newtype(
);
);
macro_rules! extract(
($obj:ident to $t:ty => $body: block) => {
impl <'python, 'source, 'prepared>
::conversion::ExtractPyObject<'python, 'source, 'prepared>
for $t
{
type Prepared = &'source PyObject<'python>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(obj)
}
fn extract(&$obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, Self> {
$body
}
}
}
);
mod object;
mod typeobject;
mod module;

View File

@ -24,7 +24,7 @@ use err::{self, PyResult, PyErr};
use super::object::PyObject;
use super::exc;
use ffi;
use conversion::{ToPyObject, FromPyObject};
use conversion::{ToPyObject, ExtractPyObject};
/// Represents a Python `int` object.
///
@ -118,11 +118,9 @@ macro_rules! int_fits_c_long(
}
}
#[cfg(feature="python27-sys")]
impl <'p> FromPyObject<'p> for $rust_type {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> {
let py = s.python();
let val = unsafe { ffi::PyInt_AsLong(s.as_ptr()) };
extract!(obj to $rust_type => {
let py = obj.python();
let val = unsafe { ffi::PyLong_AsLong(obj.as_ptr()) };
if val == -1 && PyErr::occurred(py) {
return Err(PyErr::fetch(py));
}
@ -130,23 +128,7 @@ macro_rules! int_fits_c_long(
Some(v) => Ok(v),
None => Err(overflow_error(py))
}
}
}
#[cfg(feature="python3-sys")]
impl <'p> FromPyObject<'p> for $rust_type {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> {
let py = s.python();
let val = unsafe { ffi::PyLong_AsLong(s.as_ptr()) };
if val == -1 && PyErr::occurred(py) {
return Err(PyErr::fetch(py));
}
match num::traits::cast::<c_long, $rust_type>(val) {
Some(v) => Ok(v),
None => Err(overflow_error(py))
}
}
}
});
)
);
@ -162,16 +144,14 @@ macro_rules! int_fits_larger_int(
}
}
impl <'p> FromPyObject<'p> for $rust_type {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> {
let py = s.python();
let val = try!(s.extract::<$larger_type>());
extract!(obj to $rust_type => {
let py = obj.python();
let val = try!(obj.extract::<$larger_type>());
match num::traits::cast::<$larger_type, $rust_type>(val) {
Some(v) => Ok(v),
None => Err(overflow_error(py))
}
}
}
});
)
);
@ -215,15 +195,24 @@ macro_rules! int_convert_u64_or_i64 (
}
}
impl <'p> FromPyObject<'p> for $rust_type {
impl <'python, 'source, 'prepared>
ExtractPyObject<'python, 'source, 'prepared> for $rust_type
{
type Prepared = &'source PyObject<'python>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(obj)
}
#[cfg(feature="python27-sys")]
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> {
let py = s.python();
let ptr = s.as_ptr();
fn extract(&obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, $rust_type> {
let py = obj.python();
let ptr = obj.as_ptr();
unsafe {
if ffi::PyLong_Check(ptr) != 0 {
err_if_invalid_value(s, !0, || $pylong_as_ull_or_ull(s.as_ptr()) )
err_if_invalid_value(&obj, !0, || $pylong_as_ull_or_ull(obj.as_ptr()) )
} else if ffi::PyInt_Check(ptr) != 0 {
match num::traits::cast::<c_long, $rust_type>(ffi::PyInt_AS_LONG(ptr)) {
Some(v) => Ok(v),
@ -237,12 +226,12 @@ macro_rules! int_convert_u64_or_i64 (
}
#[cfg(feature="python3-sys")]
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> {
let py = s.python();
let ptr = s.as_ptr();
fn extract(&obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, $rust_type> {
let py = obj.python();
let ptr = obj.as_ptr();
unsafe {
if ffi::PyLong_Check(ptr) != 0 {
err_if_invalid_value(s, !0, || $pylong_as_ull_or_ull(s.as_ptr()) )
err_if_invalid_value(&obj, !0, || $pylong_as_ull_or_ull(obj.as_ptr()) )
} else {
let num = try!(err::result_from_owned_ptr(py, ffi::PyNumber_Long(ptr)));
err_if_invalid_value(&num, !0, || $pylong_as_ull_or_ull(num.as_ptr()) )
@ -291,17 +280,15 @@ impl <'p> ToPyObject<'p> for f64 {
}
}
impl <'p> FromPyObject<'p> for f64 {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, f64> {
let py = s.python();
let v = unsafe { ffi::PyFloat_AsDouble(s.as_ptr()) };
extract!(obj to f64 => {
let py = obj.python();
let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
if v == -1.0 && PyErr::occurred(py) {
Err(PyErr::fetch(py))
} else {
Ok(v)
}
}
}
});
fn overflow_error(py: Python) -> PyErr {
PyErr::new_lazy_init(py.get_type::<exc::OverflowError>(), None)
@ -315,11 +302,9 @@ impl <'p> ToPyObject<'p> for f32 {
}
}
impl <'p> FromPyObject<'p> for f32 {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, f32> {
Ok(try!(s.extract::<f64>()) as f32)
}
}
extract!(obj to f32 => {
Ok(try!(obj.extract::<f64>()) as f32)
});
#[cfg(test)]
mod test {

View File

@ -243,8 +243,10 @@ impl <'p> PyObject<'p> {
/// Extracts some type from the Python object.
/// This is a wrapper function around `FromPyObject::from_py_object()`.
#[inline]
pub fn extract<T>(&self) -> Result<T, PyErr<'p>> where T: ::conversion::FromPyObject<'p> {
::conversion::FromPyObject::from_py_object(self)
pub fn extract<'s, T>(&'s self) -> Result<T, PyErr<'p>>
where T: for<'prep> ::conversion::ExtractPyObject<'p, 's, 'prep> {
let prepared = try!(<T as ::conversion::ExtractPyObject>::prepare_extract(self));
<T as ::conversion::ExtractPyObject>::extract(&prepared)
}
}

View File

@ -25,7 +25,7 @@ use ffi;
use python::{Python, PythonObject, ToPythonPointer};
use super::{exc, PyObject};
use err::{self, PyResult, PyErr};
use conversion::{FromPyObject, ToPyObject};
use conversion::{ExtractPyObject, ToPyObject};
/// Represents a Python byte string.
/// Corresponds to `str` in Python 2, and `bytes` in Python 3.
@ -281,16 +281,40 @@ impl <'p> ToPyObject<'p> for String {
}
}
/// Allows extracting strings from Python objects.
/// Accepts Python `str` and `unicode` objects.
/// In Python 2.7, `str` is expected to be UTF-8 encoded.
extract!(obj to String => {
PyString::extract(obj).map(|s| s.into_owned())
});
/// Allows extracting strings from Python objects.
/// Accepts Python `str` and `unicode` objects.
/// In Python 2.7, `str` is expected to be UTF-8 encoded.
impl <'p> FromPyObject<'p> for String {
fn from_py_object(o: &PyObject<'p>) -> PyResult<'p, String> {
PyString::extract(o).map(|s| s.into_owned())
extract!(obj to Cow<'source, str> => {
PyString::extract(obj)
});
impl <'python, 'source, 'prepared> ExtractPyObject<'python, 'source, 'prepared> for &'prepared str {
type Prepared = Cow<'source, str>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
PyString::extract(obj)
}
#[inline]
fn extract(cow: &'prepared Cow<'source, str>) -> PyResult<'python, Self> {
Ok(cow)
}
}
#[cfg(test)]
mod test {
use python::{Python, PythonObject};
use conversion::{ToPyObject, ExtractPyObject};
#[test]
fn test_non_bmp() {
let gil = Python::acquire_gil();
@ -299,3 +323,15 @@ fn test_non_bmp() {
let py_string = s.to_py_object(py).into_object();
assert_eq!(s, py_string.extract::<String>().unwrap());
}
#[test]
fn test_extract_str() {
let gil = Python::acquire_gil();
let py = gil.python();
let s = "Hello Python";
let py_string = s.to_py_object(py).into_object();
let prepared = <&str>::prepare_extract(&py_string).unwrap();
assert_eq!(s, <&str>::extract(&prepared).unwrap());
}
}

View File

@ -21,7 +21,7 @@ use err::{self, PyResult, PyErr};
use super::object::PyObject;
use super::exc;
use ffi::{self, Py_ssize_t};
use conversion::{ToPyObject, FromPyObject};
use conversion::{ToPyObject, ExtractPyObject};
/// Represents a Python tuple object.
pub struct PyTuple<'p>(PyObject<'p>);
@ -219,14 +219,12 @@ impl <'p> ToPyObject<'p> for NoArgs {
/// Returns `Ok(NoArgs)` if the input is an empty Python tuple.
/// Otherwise, returns an error.
impl <'p> FromPyObject<'p> for NoArgs {
fn from_py_object(s : &PyObject<'p>) -> PyResult<'p, NoArgs> {
let t = try!(s.cast_as::<PyTuple>());
extract!(obj to NoArgs => {
let t = try!(obj.cast_as::<PyTuple>());
if t.len() == 0 {
Ok(NoArgs)
} else {
Err(wrong_tuple_length(t, 0))
}
}
}
});