refactor __new__ method implementation
This commit is contained in:
parent
3b00145040
commit
ed8599cfd3
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() }
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue