refactor __new__ method implementation

This commit is contained in:
Nikolay Kim 2017-08-08 00:27:33 -07:00
parent 3b00145040
commit ed8599cfd3
8 changed files with 208 additions and 41 deletions

View file

@ -76,8 +76,8 @@ attribute. Only python `__new__` method can be specified, `__init__` is not avai
impl MyClass {
#[new]
fn __new__(cls: &PyType, ...) -> PyResult<Py<MyClass>> {
cls.py().init(|token| {
fn __new__(obj: &PyRawObject, ...) -> PyResult<()> {
obj.init(|token| {
MyClass {
num: 10,
debug: False,
@ -92,9 +92,10 @@ Some rules of `new` method
* If no method marked with `#[new]` is declared, object instances can only be created
from Rust, but not from Python.
* The first parameter is the type object of the class to create.
This may be the type object of a derived class declared in Python.
* The first parameter implicitly has type `&PyType`.
* The first parameter is the raw object, custom `new` method must initialize object
with value of struct using `init` method. Type of the object may be the type object of
a derived class declared in Python.
* The first parameter implicitly has type `&PyRawObject`.
* For details on `parameter-list`, see the documentation of `Method arguments` section.
* The return type must be `PyResult<T>` for some `T` that implements `IntoPyObject`.
Usually, `T` will be `MyType`.
@ -103,22 +104,45 @@ Some rules of `new` method
## Inheritance
By default `PyObject` is used as default base class. To override default base class
`base` parameter to `py::class` needs to be used. Value is full path to base class.
`base` parameter for `py::class` needs to be used. Value is full path to base class.
`__new__` method accepts `PyRawObject` object. `obj` instance must be initialized
with value of custom class struct. Subclass must call parent's `__new__` method.
```rust
#[py::class]
struct BaseClass {
val1: usize
}
#[py::class]
impl BaseClass {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| BaseClass{val1: 10})
}
pub fn method(&self) -> PyResult<() {
Ok(())
}
}
#[py::class(base=BaseClass)]
struct MyEventLoop {
fn method2(&self) -> PyResult<()> {
struct SubClass {
val2: usize
}
#[py::class]
impl SubClass {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| SubClass{val2: 10})
BaseClass::__new__(obj)
}
fn method2(&self) -> PyResult<()> {
self.get_base().method()
}
}
}
```

View file

@ -499,7 +499,7 @@ fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>,
},
"base" => {
let mut m = String::new();
for el in elem[2..elem.len()-1].iter() {
for el in elem[2..elem.len()].iter() {
let mut t = Tokens::new();
el.to_tokens(&mut t);
m += t.as_str().trim();

View file

@ -19,7 +19,7 @@ pub fn gen_py_method<'a>(cls: &Box<syn::Ty>, name: &syn::Ident,
FnType::Fn =>
impl_py_method_def(name, doc, &spec, &impl_wrap(cls, name, &spec, true)),
FnType::FnNew =>
impl_py_method_def_new(name, doc, &impl_wrap_type(cls, name, &spec)),
impl_py_method_def_new(name, doc, &impl_wrap_new(cls, name, &spec)),
FnType::FnInit =>
impl_py_method_def_init(name, doc, &impl_wrap_init(cls, name, &spec)),
FnType::FnCall =>
@ -123,12 +123,12 @@ pub fn impl_proto_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
}
/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap_type(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens {
pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens {
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{
#cls::#name(&_cls, #(#names),*)
#cls::#name(&_obj, #(#names),*)
}};
let body = impl_arg_params(spec, cb);
@ -141,18 +141,33 @@ pub fn impl_wrap_type(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> T
_args: *mut _pyo3::ffi::PyObject,
_kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
{
use pyo3::typeob::PyTypeInfo;
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#name),"()");
let _pool = _pyo3::GILPool::new();
let _py = _pyo3::Python::assume_gil_acquired();
let _cls = _pyo3::PyType::from_type_ptr(_py, _cls);
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
match _pyo3::typeob::PyRawObject::new(_py, #cls::type_object(), _cls) {
Ok(_obj) => {
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = {
#body
};
_pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
let _result: #output = {
#body
};
match _result {
Ok(_) => _obj.into_ptr(),
Err(e) => {
//e.restore(_py);
std::ptr::null_mut()
}
}
}
Err(e) => {
//e.restore(_py);
std::ptr::null_mut()
}
}
}
}
}

View file

@ -18,6 +18,11 @@ use typeob::{PyTypeInfo, PyObjectAlloc};
pub struct PyToken(PhantomData<Rc<()>>);
impl PyToken {
pub(crate) fn new() -> PyToken {
PyToken(PhantomData)
}
#[inline(always)]
pub fn py(&self) -> Python {
unsafe { Python::assume_gil_acquired() }

View file

@ -155,6 +155,7 @@ pub use objects::*;
pub use objectprotocol::ObjectProtocol;
pub use object::PyObject;
pub use noargs::NoArgs;
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};

View file

@ -19,6 +19,7 @@ pub use noargs::NoArgs;
pub use python::{Python, ToPyPointer, IntoPyPointer};
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,
ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple};

View file

@ -9,8 +9,8 @@ use std::collections::HashMap;
use {ffi, class, pythonrun};
use err::{PyErr, PyResult};
use instance::Py;
use python::Python;
use instance::{Py, PyObjectWithToken, PyToken};
use python::{Python, IntoPyPointer};
use objects::PyType;
use class::methods::PyMethodDefType;
@ -55,7 +55,6 @@ pub trait PyTypeInfo {
}
/// type object supports python GC
pub const PY_TYPE_FLAG_GC: usize = 1<<0;
@ -92,7 +91,93 @@ impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo {
default fn is_exact_instance(ptr: *mut ffi::PyObject) -> bool {
<T as PyTypeInfo>::is_exact_instance(ptr)
}
}
#[allow(dead_code)]
pub struct PyRawObject {
ptr: *mut ffi::PyObject,
tp_ptr: *mut ffi::PyTypeObject,
curr_ptr: *mut ffi::PyTypeObject,
initialized: usize,
}
impl PyRawObject {
#[must_use]
pub unsafe fn new(py: Python,
tp_ptr: *mut ffi::PyTypeObject,
curr_ptr: *mut ffi::PyTypeObject) -> PyResult<PyRawObject> {
let alloc = (*curr_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc);
let ptr = alloc(curr_ptr, 0);
if !ptr.is_null() {
Ok(PyRawObject {
ptr: ptr,
tp_ptr: tp_ptr,
curr_ptr: curr_ptr,
initialized: 0,
})
} else {
PyErr::fetch(py).into()
}
}
/// Initialize memory using value.
/// `PyRawObject` is used by class `__new__` method.
/// ```
/// #[py::class]
/// struct MyClass {
/// token: PyToken
/// }
///
/// #[py::methods]
/// impl MyClass {
/// #[new]
/// fn __new__(obj: &PyRawObject) -> PyResult<()> {
/// obj.init(|token| MyClass{token| token})
/// MyClass::BaseType::__new__(obj)
/// }
/// }
/// ```
pub fn init<T, F>(&self, f: F) -> PyResult<()>
where F: FnOnce(PyToken) -> T,
T: PyTypeInfo
{
let value = f(PyToken::new());
unsafe {
let ptr = (self.ptr as *mut u8).offset(T::OFFSET) as *mut T;
std::ptr::write(ptr, value);
}
Ok(())
}
/// Type object
pub fn type_object(&self) -> &PyType {
unsafe {PyType::from_type_ptr(self.py(), self.curr_ptr)}
}
/// Return reference to object.
pub fn as_ref<T: PyTypeInfo>(&self) -> &T {
// TODO: check is object initialized
unsafe {
let ptr = (self.ptr as *mut u8).offset(T::OFFSET) as *mut T;
ptr.as_ref().unwrap()
}
}
}
impl IntoPyPointer for PyRawObject {
fn into_ptr(self) -> *mut ffi::PyObject {
// TODO: panic if not all types initialized
return self.ptr
}
}
impl PyObjectWithToken for PyRawObject {
#[inline(always)]
fn py(&self) -> Python {
unsafe { Python::assume_gil_acquired() }
}
}
/// A Python object allocator that is usable as a base type for #[class]
@ -212,8 +297,8 @@ pub fn initialize_type<'p, T>(py: Python<'p>, module_name: Option<&str>) -> PyRe
"Module name/type name must not contain NUL byte").into_raw();
let type_object: &mut ffi::PyTypeObject = unsafe{&mut *T::type_object()};
let base_type_object: &mut ffi::PyTypeObject = unsafe{
&mut *<T::BaseType as PyTypeInfo>::type_object()};
let base_type_object: &mut ffi::PyTypeObject = unsafe {
&mut *<T::BaseType as PyTypeInfo>::type_object() };
type_object.tp_name = name;
type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _;
@ -322,7 +407,7 @@ pub fn initialize_type<'p, T>(py: Python<'p>, module_name: Option<&str>) -> PyRe
if ffi::PyType_Ready(type_object) == 0 {
Ok(())
} else {
Err(PyErr::fetch(py))
PyErr::fetch(py).into()
}
}
}

View file

@ -100,8 +100,8 @@ struct EmptyClassWithNew {
#[py::methods]
impl EmptyClassWithNew {
#[__new__]
fn __new__(cls: &PyType) -> PyResult<Py<EmptyClassWithNew>> {
Ok(Py::new(cls.py(), |t| EmptyClassWithNew{token: t})?.into())
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| EmptyClassWithNew{token: t})
}
}
@ -122,8 +122,8 @@ struct NewWithOneArg {
#[py::methods]
impl NewWithOneArg {
#[new]
fn __new__(cls: &PyType, arg: i32) -> PyResult<&mut NewWithOneArg> {
cls.py().init_mut(|t| NewWithOneArg{_data: arg, token: t})
fn __new__(obj: &PyRawObject, arg: i32) -> PyResult<()> {
obj.init(|t| NewWithOneArg{_data: arg, token: t})
}
}
@ -148,11 +148,9 @@ struct NewWithTwoArgs {
#[py::methods]
impl NewWithTwoArgs {
#[new]
fn __new__(cls: &PyType, arg1: i32, arg2: i32) -> PyResult<Py<NewWithTwoArgs>>
fn __new__(obj: &PyRawObject, arg1: i32, arg2: i32) -> PyResult<()>
{
Py::new(
cls.py(),
|t| NewWithTwoArgs{_data1: arg1, _data2: arg2, token: t})
obj.init(|t| NewWithTwoArgs{_data1: arg1, _data2: arg2, token: t})
}
}
@ -161,7 +159,7 @@ fn new_with_two_args() {
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<NewWithTwoArgs>();
let wrp = typeobj.call((10, 20), NoArgs).unwrap();
let wrp = typeobj.call((10, 20), NoArgs).map_err(|e| e.print(py)).unwrap();
let obj = wrp.cast_as::<NewWithTwoArgs>().unwrap();
assert_eq!(obj._data1, 10);
assert_eq!(obj._data2, 20);
@ -339,8 +337,8 @@ struct ClassMethod {token: PyToken}
#[py::methods]
impl ClassMethod {
#[new]
fn __new__(cls: &PyType) -> PyResult<Py<ClassMethod>> {
cls.py().init(|t| ClassMethod{token: t})
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| ClassMethod{token: t})
}
#[classmethod]
@ -390,8 +388,8 @@ struct StaticMethod {
#[py::methods]
impl StaticMethod {
#[new]
fn __new__(cls: &PyType) -> PyResult<&StaticMethod> {
Ok(cls.py().init_mut(|t| StaticMethod{token: t})?.into())
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| StaticMethod{token: t})
}
#[staticmethod]
@ -1185,7 +1183,6 @@ impl ClassWithProperties {
}
}
#[test]
fn class_with_properties() {
let gil = Python::acquire_gil();
@ -1306,3 +1303,42 @@ fn getter_setter_autogen() {
py_run!(py, inst, "assert inst.num == 10");
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
}
#[py::class]
struct BaseClass {
#[prop(get)]
val1: usize
}
#[py::methods]
impl BaseClass {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| BaseClass{val1: 10})
}
}
#[py::class(base=BaseClass)]
struct SubClass {
#[prop(get)]
val2: usize
}
#[py::methods]
impl SubClass {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| SubClass{val2: 5})?;
BaseClass::__new__(obj)
}
}
#[test]
fn inheritance_with_new_methods() {
let gil = Python::acquire_gil();
let py = gil.python();
let typebase = py.get_type::<BaseClass>();
let typeobj = py.get_type::<SubClass>();
let inst = typeobj.call(NoArgs, NoArgs).unwrap();
py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5");
}