py_class!: add support for instance methods

This commit is contained in:
Daniel Grunwald 2016-03-12 17:58:50 +01:00
parent ab83b4f4a1
commit cc81a01077
7 changed files with 343 additions and 54 deletions

View File

@ -38,7 +38,7 @@ macro_rules! py_method_def {
};
method_def.ml_name = concat!($name, "\0").as_ptr() as *const _;
method_def.ml_meth = Some(
std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords,
::std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords,
$crate::_detail::ffi::PyCFunction>($wrap)
);
&mut method_def

View File

@ -148,7 +148,7 @@ pub mod _detail {
pub mod libc {
pub use ::libc::c_char;
}
pub use err::from_owned_ptr_or_panic;
pub use err::{from_owned_ptr_or_panic, result_from_owned_ptr};
pub use function::{handle_callback, py_fn_impl, AbortOnDrop};
}

View File

@ -68,8 +68,6 @@ macro_rules! pyobject_to_pyobject(
)
);
#[doc(hidden)]
#[macro_export]
macro_rules! pyobject_newtype(
($name: ident) => (
pyobject_to_pyobject!($name);
@ -100,28 +98,6 @@ macro_rules! pyobject_newtype(
}
}
);
($name: ident, downcast using typeobject) => (
pyobject_newtype!($name);
impl $crate::PythonObjectWithCheckedDowncast for $name {
#[inline]
fn downcast_from<'p>(py: $crate::Python<'p>, obj: $crate::PyObject) -> Result<$name, $crate::PythonObjectDowncastError<'p>> {
if py.get_type::<$name>().is_instance(py, &obj) {
Ok($name(obj))
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
#[inline]
fn downcast_borrow_from<'a, 'p>(py: $crate::Python<'p>, obj: &'a $crate::PyObject) -> Result<&'a $name, $crate::PythonObjectDowncastError<'p>> {
if py.get_type::<$name>().is_instance(py, obj) {
unsafe { Ok(::std::mem::transmute(obj)) }
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
}
);
($name: ident, $checkfunction: ident) => (
pyobject_newtype!($name);

109
src/py_class/members.rs Normal file
View File

@ -0,0 +1,109 @@
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use std::marker;
use python::{Python, PythonObject};
use conversion::ToPyObject;
use objects::PyObject;
use err::{self, PyResult};
use ffi;
/// Represents something that can be added as a member to a Python class/type.
///
/// T: type of rust class used for instances of the Python class/type.
pub trait TypeMember<T> where T: PythonObject {
/// Convert the type member into a python object
/// that can be stored in the type dict.
unsafe fn into_descriptor(self, py: Python, ty: *mut ffi::PyTypeObject) -> PyResult<PyObject>;
}
impl <T, S> TypeMember<T> for S where T: PythonObject, S: ToPyObject {
#[inline]
unsafe fn into_descriptor(self, py: Python, _ty: *mut ffi::PyTypeObject) -> PyResult<PyObject> {
Ok(self.into_py_object(py).into_object())
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_init_members {
($class:ident, $py:ident, $type_object: ident, { }) => {{}};
($class:ident, $py:ident, $type_object: ident, { $( $name:ident = $init:expr; )+ }) => {{
let dict = $crate::PyDict::new($py);
$( {
// keep $init out of unsafe block; it might contain user code
let init = $init;
let descriptor = try!(unsafe {
$crate::py_class::members::TypeMember::<$class>::into_descriptor(init, $py, &mut $type_object)
});
try!(dict.set_item($py, stringify!($name), descriptor));
})*
unsafe {
assert!($type_object.tp_dict.is_null());
$type_object.tp_dict = $crate::PythonObject::into_object(dict).steal_ptr();
}
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_instance_method {
($py:ident, $class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_instance_method<DUMMY>(
slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION,
|py| {
py_argparse_raw!(py, Some(LOCATION), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let ret = slf.$f(py $(, $pname )* );
$crate::PyDrop::release_ref(slf, py);
ret
})
})
}
unsafe {
let method_def = py_method_def!(stringify!($f), 0, wrap_instance_method::<()>);
$crate::py_class::members::create_instance_method_descriptor(method_def)
}
}}
}
pub struct InstanceMethodDescriptor<T>(*mut ffi::PyMethodDef, marker::PhantomData<fn(&T)>);
#[inline]
pub unsafe fn create_instance_method_descriptor<T>(method_def: *mut ffi::PyMethodDef)
-> InstanceMethodDescriptor<T>
{
InstanceMethodDescriptor(method_def, marker::PhantomData)
}
impl <T> TypeMember<T> for InstanceMethodDescriptor<T> where T: PythonObject {
#[inline]
unsafe fn into_descriptor(self, py: Python, ty: *mut ffi::PyTypeObject) -> PyResult<PyObject> {
err::result_from_owned_ptr(py, ffi::PyDescr_NewMethod(ty, self.0))
}
}

View File

@ -18,6 +18,7 @@
mod py_class;
#[doc(hidden)] pub mod slots;
#[doc(hidden)] pub mod members;
use libc;
use std::{mem, ptr};

View File

@ -17,6 +17,19 @@
// DEALINGS IN THE SOFTWARE.
/**
Defines new python extension class.
A `py_class!` macro invocation generates code that declares a new Python class.
Additionally, it generates a Rust struct of the same name, which allows accessing
instances of that Python class from Rust.
# Syntax
`py_class!(class MyType |py| { ... })`
* `MyType` is the name of the Python class.
* `py` is an identifier that will be made available as a variable of type `Python`
in all function bodies.
* `{ ... }` is the class body, described in more detail below.
# Example
```
#[macro_use] extern crate cpython;
@ -27,10 +40,10 @@ py_class!(class MyType |py| {
def __new__(_cls, arg: i32) -> PyResult<MyType> {
MyType::create_instance(py, arg)
}
/*def half(&self) -> PyResult<i32> {
def half(&self) -> PyResult<i32> {
println!("half() was called with self={:?}", self.number(py));
Ok(self.number(py) / 2)
}*/
}
});
fn main() {
@ -40,7 +53,95 @@ fn main() {
dict.set_item(py, "MyType", py.get_type::<MyType>()).unwrap();
py.run("assert MyType(42).half() == 21", None, Some(&dict)).unwrap();
}
``` */
```
# Generated Rust type
The above example generates the following Rust type:
```ignore
struct MyType { ... }
impl ToPythonObject for MyType { ... }
impl PythonObject for MyType { ... }
impl PythonObjectWithCheckedDowncast for MyType { ... }
impl PythonObjectWithTypeObject for MyType { ... }
impl PythonObjectFromPyClassMacro for MyType { ... }
impl MyType {
fn create_instance(py: Python, number: i32) -> PyResult<MyType> { ... }
// data accessors
fn number<'a>(&'a self, py: Python<'a>) -> &'a i32 { ... }
// functions callable from python
pub fn __new__(_cls: &PyType, py: Python, arg: i32) -> PyResult<MyType> {
MyType::create_instance(py, arg)
}
pub fn half(&self, py: Python) -> PyResult<i32> {
println!("half() was called with self={:?}", self.number(py));
Ok(self.number(py) / 2)
}
}
```
* The generated type implements a number of traits from the `cpython` crate.
* The inherent `create_instance` method can create new Python objects
given the values for the data fields.
* Private accessors functions are created for the data fields.
* All functions callable from Python are also exposed as public Rust functions.
* To convert from `MyType` to `PyObject`, use `as_object()` or `into_object()` (from the `PythonObject` trait).
* To convert `PyObject` to `MyType`, use `obj.cast_as::<MyType>(py)` or `obj.cast_into::<MyType>(py)`.
# py_class body
The body of a `py_class!` supports the following definitions:
## Data declarations
`data data_name: data_type;`
Declares a data field within the Python class.
Used to store Rust data directly in the Python object instance.
Because Python code can pass all Python objects to other threads,
`type` must be `Send + 'static`.
Because Python object instances can be freely shared (Python has no concept of "ownership"),
data fields cannot be declared as `mut`.
If mutability is required, you have to use interior mutability (`Cell` or `RefCell`).
Data declarations are not accessible from Python.
On the Rust side, data is accessed through the automatically generated accessor functions:
```ignore
impl MyType {
fn data_name<'a>(&'a self, py: Python<'a>) -> &'a data_type { ... }
}
```
## Instance methods
`def method_name(&self, parameter-list) -> PyResult<...> { ... }`
Declares an instance method callable from Python.
* Because Python objects are potentially shared, the self parameter must always
be a shared reference (`&self`).
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## __new__
`def __new__(cls, parameter-list) -> PyResult<...> { ... }`
Declares a constructor method callable 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.
* If no `__new__` method is declared, object instances can only be created from Rust (via `MyType::create_instance`),
but not from Python.
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
Usually, `T` will be `MyType`.
*/
#[macro_export]
macro_rules! py_class {
(class $class:ident |$py: ident| { $( $body:tt )* }) => (
@ -58,7 +159,7 @@ macro_rules! py_class {
/* as_sequence */ [ /* slot: expr, */ ]
}
/* impls: */ { /* impl body */ }
/* members: */ { /* ident: expr, */ };
/* members: */ { /* ident = expr; */ };
$( $body )*
}
);
@ -78,8 +179,55 @@ macro_rules! py_class_impl {
}
$slots:tt { $( $imp:item )* } $members:tt;
} => {
struct $class($crate::PyObject);
pyobject_newtype!($class, downcast using typeobject);
struct $class { _unsafe_inner: $crate::PyObject }
pyobject_to_pyobject!($class);
impl $crate::PythonObject for $class {
#[inline]
fn as_object(&self) -> &$crate::PyObject {
&self._unsafe_inner
}
#[inline]
fn into_object(self) -> $crate::PyObject {
self._unsafe_inner
}
/// Unchecked downcast from PyObject to Self.
/// Undefined behavior if the input object does not have the expected type.
#[inline]
unsafe fn unchecked_downcast_from(obj: $crate::PyObject) -> Self {
$class { _unsafe_inner: obj }
}
/// Unchecked downcast from PyObject to Self.
/// Undefined behavior if the input object does not have the expected type.
#[inline]
unsafe fn unchecked_downcast_borrow_from<'a>(obj: &'a $crate::PyObject) -> &'a Self {
::std::mem::transmute(obj)
}
}
impl $crate::PythonObjectWithCheckedDowncast for $class {
#[inline]
fn downcast_from<'p>(py: $crate::Python<'p>, obj: $crate::PyObject) -> Result<$class, $crate::PythonObjectDowncastError<'p>> {
if py.get_type::<$class>().is_instance(py, &obj) {
Ok($class { _unsafe_inner: obj })
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
#[inline]
fn downcast_borrow_from<'a, 'p>(py: $crate::Python<'p>, obj: &'a $crate::PyObject) -> Result<&'a $class, $crate::PythonObjectDowncastError<'p>> {
if py.get_type::<$class>().is_instance(py, obj) {
unsafe { Ok(::std::mem::transmute(obj)) }
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
}
py_coerce_item! {
impl $crate::py_class::BaseObject for $class {
@ -116,7 +264,7 @@ macro_rules! py_class_impl {
py, &py.get_type::<$class>(), ( $($data_name,)* )
)
});
return Ok($class(obj));
return Ok($class { _unsafe_inner: obj });
// hide statics in create_instance to avoid name conflicts
static mut type_object : $crate::_detail::ffi::PyTypeObject
@ -155,12 +303,15 @@ macro_rules! py_class_impl {
}
}
unsafe fn init(py: $crate::Python) -> $crate::PyResult<$crate::PyType> {
py_class_type_object_dynamic_init!(py, type_object, $class);
if $crate::_detail::ffi::PyType_Ready(&mut type_object) == 0 {
Ok($crate::PyType::from_type_ptr(py, &mut type_object))
} else {
Err($crate::PyErr::fetch(py))
fn init($py: $crate::Python) -> $crate::PyResult<$crate::PyType> {
py_class_type_object_dynamic_init!($class, $py, type_object);
py_class_init_members!($class, $py, type_object, $members);
unsafe {
if $crate::_detail::ffi::PyType_Ready(&mut type_object) == 0 {
Ok($crate::PyType::from_type_ptr($py, &mut type_object))
} else {
Err($crate::PyErr::fetch($py))
}
}
}
}
@ -199,7 +350,7 @@ macro_rules! py_class_impl {
unsafe {
$crate::py_class::data_get::<$data_type>(
py,
&self.0,
&self._unsafe_inner,
$crate::py_class::data_offset::<$data_type>($size)
)
}
@ -230,7 +381,7 @@ macro_rules! py_class_impl {
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __new__($cls: &$crate::PyType) $res_type; { $($body)* } [] }
py_class_impl_item! { $class, $py, __new__($cls: &$crate::PyType,) $res_type; { $($body)* } [] }
}
$members;
$($tail)*
@ -259,25 +410,75 @@ macro_rules! py_class_impl {
/* impl: */ {
$($imp)*
py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, __new__($cls: &$crate::PyType) $res_type; { $($body)* } }
py_class_impl_item { $class, $py, __new__($cls: &$crate::PyType,) $res_type; { $($body)* } }
[] ($($p)+,)
}
}
$members;
$($tail)*
}};
// def __init__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __init__ $($tail:tt)*
} => {
py_error! { "__init__ is not supported by py_class!; use __new__ instead." }
};
// def instance_method(&self)
{ $class:ident $py:ident $info:tt $slots:tt
{ $( $imp:item )* }
{ $( $member_name:ident = $member_expr:expr; )* };
def $name:ident (&$slf:ident)
-> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info $slots
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, $name(&$slf,) $res_type; { $($body)* } [] }
}
/* members: */ {
$( $member_name:ident = $member_expr:expr; )*
$name = py_class_instance_method!{$py, $class::$name []};
};
$($tail)*
}};
// def instance_method(&self, params)
{ $class:ident $py:ident $info:tt $slots:tt
{ $( $imp:item )* }
{ $( $member_name:ident = $member_expr:expr; )* };
def $name:ident (&$slf:ident, $($p:tt)+)
-> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info $slots
/* impl: */ {
$($imp)*
py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, $name(&$slf,) $res_type; { $($body)* } }
[] ($($p)+,)
}
}
/* members: */ {
$( $member_name:ident = $member_expr:expr; )*
$name = py_argparse_parse_plist_impl!{
py_class_instance_method!{$py, $class::$name}
[] ($($p)+,)
};
};
$($tail)*
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_impl_item {
{ $class:ident, $py:ident, $name:ident( $( $pname1:ident : $ptype1:ty ),* )
$res_type:ty; $body:block [ $( { $pname2:ident : $ptype2:ty = $detail:tt } )* ]
} => {
{ $class:ident, $py:ident, $name:ident( $( $selfarg:tt )* )
$res_type:ty; $body:block [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]
} => { py_coerce_item! {
impl $class {
pub fn __new__($py: $crate::Python $( , $pname1: $ptype1 )* $( , $pname2: $ptype2 )* )
pub fn $name($( $selfarg )* $py: $crate::Python $( , $pname: $ptype )* )
-> $res_type $body
}
}
}}
}

View File

@ -42,11 +42,13 @@ macro_rules! py_class_type_object_static_init {
#[doc(hidden)]
macro_rules! py_class_type_object_dynamic_init {
// initialize those fields of PyTypeObject that we couldn't initialize statically
($py:ident, $type_object:ident, $class_name: ident) => {{
$type_object.tp_name = concat!(stringify!($class_name), "\0").as_ptr() as *const _;
$type_object.tp_basicsize = <$class_name as $crate::py_class::BaseObject>::size()
as $crate::_detail::ffi::Py_ssize_t;
}}
($class: ident, $py:ident, $type_object:ident) => {
unsafe {
$type_object.tp_name = concat!(stringify!($class), "\0").as_ptr() as *const _;
$type_object.tp_basicsize = <$class as $crate::py_class::BaseObject>::size()
as $crate::_detail::ffi::Py_ssize_t;
}
}
}
pub unsafe extern "C" fn tp_dealloc_callback<T>(obj: *mut ffi::PyObject)
@ -69,7 +71,7 @@ macro_rules! py_class_wrap_newfunc {
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!(), "()");
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION,
|py| {
@ -77,7 +79,7 @@ macro_rules! py_class_wrap_newfunc {
[ $( { $pname : $ptype = $detail } )* ]
{
let cls = $crate::PyType::from_type_ptr(py, cls);
let ret = $class::$f(py, &cls $(, $pname )* );
let ret = $class::$f(&cls, py $(, $pname )* );
$crate::PyDrop::release_ref(cls, py);
ret
})