From 324a6b26975765862768cf5679da21d4cb87fc03 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Jan 2018 09:04:42 -0800 Subject: [PATCH] drop RefFromPyObject; allow mut refs #106 --- CHANGES.txt | 8 ++- Cargo.toml | 6 +- pyo3cls/Cargo.toml | 2 +- pyo3cls/src/py_method.rs | 131 +++++++++---------------------------- src/conversion.rs | 53 ++++++--------- src/lib.rs | 2 +- src/objects/stringutils.rs | 34 +++++----- src/prelude.rs | 2 +- src/python.rs | 7 ++ src/pythonrun.rs | 25 ++++--- tests/test_class.rs | 34 ++++++++++ 11 files changed, 141 insertions(+), 163 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 740e9578..4058c3d8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,13 @@ Changes ------- -0.2.4 (2018-01-xx) +0.2.4 (2018-01-19) + +* Allow to get mutable ref from PyObject #106 + +* Drop `RefFromPyObject` trait + +* Add Python::register_any() method * Fix impl `FromPyObject` for `Py` diff --git a/Cargo.toml b/Cargo.toml index c5c78da7..77077f61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3" -version = "0.2.3" +version = "0.2.4" description = "Bindings to Python interpreter" authors = ["PyO3 Project and Contributors"] readme = "README.md" @@ -19,11 +19,11 @@ appveyor = { repository = "PyO3/pyo3" } codecov = { repository = "PyO3/pyo3", branch = "master", service = "github" } [dependencies] -log = "0.3" +log = "0.4" libc = "0.2" spin = "0.4.6" num-traits = "0.1" -pyo3cls = { path = "pyo3cls", version = "0.2" } +pyo3cls = { path = "pyo3cls", version = "^0.2.1" } [build-dependencies] regex = "0.2" diff --git a/pyo3cls/Cargo.toml b/pyo3cls/Cargo.toml index f2be9d64..2995cf60 100644 --- a/pyo3cls/Cargo.toml +++ b/pyo3cls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3cls" -version = "0.2.0" +version = "0.2.1" description = "Proc macros for PyO3 package" authors = ["PyO3 Project and Contributors Toke syn::Ident::from("None").to_tokens(&mut default); } - if arg.reference { - quote! { + quote! { + match match _iter.next().unwrap().as_ref() { - Some(obj) => { - if obj.is_none() { - let #arg_name = #default; - #body - } else { - match _pyo3::RefFromPyObject::with_extracted( - obj, |#name| { - let #arg_name = Some(#name); - #body - }) - { - Ok(v) => v, - Err(e) => Err(e) - } - } - }, - None => { - let #arg_name = #default; - #body - } - } - } - } else { - quote! { - match - match _iter.next().unwrap().as_ref() { - Some(_obj) => { - if _obj.is_none() { - Ok(#default) - } else { - match _obj.extract() { - Ok(_obj) => Ok(Some(_obj)), - Err(e) => Err(e) - } - } - }, - None => Ok(#default) } - { - Ok(#arg_name) => #body, - Err(e) => Err(e) - } - } - } - } else if let Some(default) = spec.default_value(name) { - if arg.reference { - quote! { - match _iter.next().unwrap().as_ref() { - Some(_obj) => { - if _obj.is_none() { - let #arg_name = #default; - #body - } else { - match _pyo3::RefFromPyObject::with_extracted( - _obj, |#arg_name| { - #body - }) - { - Ok(v) => v, - Err(e) => Err(e) - } - } - }, - None => { - let #arg_name = #default; - #body - } - } - } - } else { - quote! { - match match _iter.next().unwrap().as_ref() { Some(_obj) => { if _obj.is_none() { Ok(#default) } else { match _obj.extract() { - Ok(_obj) => Ok(_obj), - Err(e) => Err(e), + Ok(_obj) => Ok(Some(_obj)), + Err(e) => Err(e) } } }, - None => Ok(#default) - } { - Ok(#arg_name) => #body, - Err(e) => Err(e) - } + None => Ok(#default) } + { + Ok(#arg_name) => #body, + Err(e) => Err(e) + } + } + } else if let Some(default) = spec.default_value(name) { + quote! { + match match _iter.next().unwrap().as_ref() { + Some(_obj) => { + if _obj.is_none() { + Ok(#default) + } else { + match _obj.extract() { + Ok(_obj) => Ok(_obj), + Err(e) => Err(e), + } + } + }, + None => Ok(#default) + } { + Ok(#arg_name) => #body, + Err(e) => Err(e) } } } else { - if arg.reference { - quote! { - match _pyo3::RefFromPyObject::with_extracted( - _iter.next().unwrap().as_ref().unwrap(), |#arg_name| { - #body - }) - { - Ok(v) => v, - Err(e) => Err(e) - } - } - } else { - quote! { - match _iter.next().unwrap().as_ref().unwrap().extract() - { - Ok(#arg_name) => { - #body - } - Err(e) => Err(e) + quote! { + match _iter.next().unwrap().as_ref().unwrap().extract() { + Ok(#arg_name) => { + #body } + Err(e) => Err(e) } } } diff --git a/src/conversion.rs b/src/conversion.rs index 13c738b5..a65b8bbd 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -48,7 +48,6 @@ impl ToBorrowedObject for T where T: ToPyObject { pub trait IntoPyObject { /// Converts self into a Python object. (Consumes self) - #[inline] fn into_object(self, py: Python) -> PyObject; } @@ -86,25 +85,6 @@ pub trait FromPyObject<'source> : Sized { fn extract(ob: &'source PyObjectRef) -> PyResult; } -pub trait RefFromPyObject { - fn with_extracted(ob: &PyObjectRef, f: F) -> PyResult - where F: FnOnce(&Self) -> R; -} - -impl RefFromPyObject for T - where for<'a> &'a T: FromPyObject<'a> + Sized -{ - #[inline] - fn with_extracted(obj: &PyObjectRef, f: F) -> PyResult - where F: FnOnce(&Self) -> R - { - match FromPyObject::extract(obj) { - Ok(val) => Ok(f(val)), - Err(e) => Err(e) - } - } -} - /// Identity conversion: allows using existing `PyObject` instances where /// `T: ToPyObject` is expected. impl <'a, T: ?Sized> ToPyObject for &'a T where T: ToPyObject { @@ -115,7 +95,6 @@ impl <'a, T: ?Sized> ToPyObject for &'a T where T: ToPyObject { } } - /// `Option::Some` is converted like `T`. /// `Option::None` is converted to Python `None`. impl ToPyObject for Option where T: ToPyObject { @@ -127,6 +106,7 @@ impl ToPyObject for Option where T: ToPyObject { } } } + impl IntoPyObject for Option where T: IntoPyObject { fn into_object(self, py: Python) -> PyObject { @@ -158,8 +138,7 @@ impl<'a, T> IntoPyObject for &'a T where T: ToPyPointer } } -impl<'a, T> IntoPyObject for &'a mut T where T: ToPyPointer -{ +impl<'a, T> IntoPyObject for &'a mut T where T: ToPyPointer { #[inline] fn into_object<'p>(self, py: Python) -> PyObject { unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } @@ -171,16 +150,24 @@ impl<'a, T> FromPyObject<'a> for &'a T where T: PyTypeInfo { #[inline] - default fn extract(ob: &'a PyObjectRef) -> PyResult<&'a T> - { + default fn extract(ob: &'a PyObjectRef) -> PyResult<&'a T> { Ok(T::try_from(ob)?) } } -impl<'source, T> FromPyObject<'source> for Option where T: FromPyObject<'source> +/// Extract mutable reference to instance from `PyObject` +impl<'a, T> FromPyObject<'a> for &'a mut T + where T: PyTypeInfo { - fn extract(obj: &'source PyObjectRef) -> PyResult - { + #[inline] + default fn extract(ob: &'a PyObjectRef) -> PyResult<&'a mut T> { + Ok(T::try_from_mut(ob)?) + } +} + +impl<'a, T> FromPyObject<'a> for Option where T: FromPyObject<'a> +{ + fn extract(obj: &'a PyObjectRef) -> PyResult { if obj.as_ptr() == unsafe { ffi::Py_None() } { Ok(None) } else { @@ -192,7 +179,6 @@ impl<'source, T> FromPyObject<'source> for Option where T: FromPyObject<'sour } } - /// Trait implemented by Python object types that allow a checked downcast. /// This trait is similar to `std::convert::TryInto` pub trait PyTryInto: Sized { @@ -232,8 +218,8 @@ pub trait PyTryFrom: Sized { } // TryFrom implies TryInto -impl PyTryInto for PyObjectRef where U: PyTryFrom -{ +impl PyTryInto for PyObjectRef where U: PyTryFrom { + type Error = U::Error; fn try_into(&self) -> Result<&U, U::Error> { @@ -251,9 +237,8 @@ impl PyTryInto for PyObjectRef where U: PyTryFrom } -impl PyTryFrom for T - where T: PyTypeInfo -{ +impl PyTryFrom for T where T: PyTypeInfo { + type Error = PyDowncastError; fn try_from(value: &PyObjectRef) -> Result<&T, Self::Error> { diff --git a/src/lib.rs b/src/lib.rs index 88b759de..7876d4ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,7 @@ pub use typeob::{PyTypeInfo, PyRawObject, PyObjectAlloc}; pub use python::{Python, ToPyPointer, IntoPyPointer, IntoPyDictPointer}; pub use pythonrun::{GILGuard, GILPool, prepare_freethreaded_python, prepare_pyo3_library}; pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType}; -pub use conversion::{FromPyObject, RefFromPyObject, PyTryFrom, PyTryInto, +pub use conversion::{FromPyObject, PyTryFrom, PyTryInto, ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple}; pub mod class; pub use class::*; diff --git a/src/objects/stringutils.rs b/src/objects/stringutils.rs index dc562d5d..9be1d97a 100644 --- a/src/objects/stringutils.rs +++ b/src/objects/stringutils.rs @@ -5,8 +5,8 @@ use err::PyResult; use python::Python; use object::PyObject; use objects::{PyObjectRef, PyString}; -use objectprotocol::ObjectProtocol; -use conversion::{ToPyObject, IntoPyObject, RefFromPyObject, PyTryFrom}; +use instance::PyObjectWithToken; +use conversion::{ToPyObject, IntoPyObject, PyTryFrom}; /// Converts Rust `str` to Python object. /// See `PyString::new` for details on the conversion. @@ -55,25 +55,29 @@ impl<'a> IntoPyObject for &'a String { /// Allows extracting strings from Python objects. /// Accepts Python `str` and `unicode` objects. -impl<'source> ::FromPyObject<'source> for Cow<'source, str> -{ - fn extract(ob: &'source PyObjectRef) -> PyResult - { +impl<'source> ::FromPyObject<'source> for Cow<'source, str> { + fn extract(ob: &'source PyObjectRef) -> PyResult { PyString::try_from(ob)?.to_string() } } +/// Allows extracting strings from Python objects. +/// Accepts Python `str` and `unicode` objects. +impl<'a> ::FromPyObject<'a> for &'a str { + fn extract(ob: &'a PyObjectRef) -> PyResult { + let s: Cow<'a, str> = ::FromPyObject::extract(ob)?; + match s { + Cow::Borrowed(r) => Ok(r), + Cow::Owned(r) => { + let r = ob.py().register_any(r); + Ok(r.as_str()) + } + } + } +} + /// Allows extracting strings from Python objects. /// Accepts Python `str` and `unicode` objects. pyobject_extract!(obj to String => { ::try_from(obj)?.to_string().map(Cow::into_owned) }); - -impl RefFromPyObject for str { - fn with_extracted(obj: &PyObjectRef, f: F) -> PyResult - where F: FnOnce(&str) -> R - { - let s = try!(obj.extract::>()); - Ok(f(&s)) - } -} diff --git a/src/prelude.rs b/src/prelude.rs index 90fcea56..bef998c0 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -21,5 +21,5 @@ pub use err::{PyErr, PyErrValue, PyResult, PyDowncastError, PyErrArguments}; pub use pythonrun::GILGuard; pub use typeob::PyRawObject; pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType}; -pub use conversion::{FromPyObject, RefFromPyObject, PyTryFrom, PyTryInto, +pub use conversion::{FromPyObject, PyTryFrom, PyTryInto, ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple}; diff --git a/src/python.rs b/src/python.rs index 7cfb9c72..9aac62ba 100644 --- a/src/python.rs +++ b/src/python.rs @@ -391,6 +391,13 @@ impl<'p> Python<'p> { } } + #[doc(hidden)] + /// Pass value owneship to `Python` object and get reference back. + /// Value get cleaned up on the GIL release. + pub fn register_any(self, ob: T) -> &'p T { + unsafe { pythonrun::register_any(ob) } + } + /// Release PyObject reference. #[inline] pub fn release(self, ob: T) where T: IntoPyPointer { diff --git a/src/pythonrun.rs b/src/pythonrun.rs index bee1e1b8..584faa20 100644 --- a/src/pythonrun.rs +++ b/src/pythonrun.rs @@ -1,8 +1,5 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -// -// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython - -use std::{sync, rc, marker, mem}; +use std::{any, sync, rc, marker, mem}; use spin; use ffi; @@ -113,16 +110,18 @@ struct ReleasePool { owned: Vec<*mut ffi::PyObject>, borrowed: Vec<*mut ffi::PyObject>, pointers: *mut Vec<*mut ffi::PyObject>, + obj: Vec>, p: spin::Mutex<*mut Vec<*mut ffi::PyObject>>, } impl ReleasePool { fn new() -> ReleasePool { ReleasePool { - owned: Vec::with_capacity(250), - borrowed: Vec::with_capacity(250), - pointers: Box::into_raw(Box::new(Vec::with_capacity(250))), - p: spin::Mutex::new(Box::into_raw(Box::new(Vec::with_capacity(250)))), + owned: Vec::with_capacity(256), + borrowed: Vec::with_capacity(256), + pointers: Box::into_raw(Box::new(Vec::with_capacity(256))), + obj: Vec::with_capacity(8), + p: spin::Mutex::new(Box::into_raw(Box::new(Vec::with_capacity(256)))), } } @@ -165,6 +164,8 @@ impl ReleasePool { if pointers { self.release_pointers(); } + + self.obj.clear(); } } @@ -206,6 +207,14 @@ impl Drop for GILPool { } } +pub unsafe fn register_any<'p, T: 'static>(obj: T) -> &'p T +{ + let pool: &'static mut ReleasePool = &mut *POOL; + + pool.obj.push(Box::new(obj)); + pool.obj.last().unwrap().as_ref().downcast_ref::().unwrap() +} + pub unsafe fn register_pointer(obj: *mut ffi::PyObject) { let pool: &'static mut ReleasePool = &mut *POOL; diff --git a/tests/test_class.rs b/tests/test_class.rs index f8bddd62..c77b0db9 100644 --- a/tests/test_class.rs +++ b/tests/test_class.rs @@ -1411,3 +1411,37 @@ fn inheritance_with_new_methods_with_drop() { assert!(drop_called1.load(Ordering::Relaxed)); assert!(drop_called2.load(Ordering::Relaxed)); } + + +#[py::class] +struct MutRefArg { + n: i32, + token: PyToken, +} + +#[py::methods] +impl MutRefArg { + + fn get(&self) -> PyResult { + Ok(self.n) + } + fn set_other(&self, other: &mut MutRefArg) -> PyResult<()> { + other.n = 100; + Ok(()) + } +} + +#[test] +fn mut_ref_arg() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst1 = py.init(|t| MutRefArg{token: t, n: 0}).unwrap(); + let inst2 = py.init(|t| MutRefArg{token: t, n: 0}).unwrap(); + + let d = PyDict::new(py); + d.set_item("inst1", &inst1).unwrap(); + d.set_item("inst2", &inst2).unwrap(); + + py.run("inst1.set_other(inst2)", None, Some(d)).unwrap(); + assert_eq!(inst2.as_ref(py).n, 100); +}