py_class!: Add support for "def __new__(cls)".

This commit is contained in:
Daniel Grunwald 2016-03-12 00:06:39 +01:00
parent 547879ef0f
commit 309182cfe8
8 changed files with 381 additions and 60 deletions

View file

@ -413,6 +413,114 @@ impl Clone for PyTypeObject {
#[inline] fn clone(&self) -> PyTypeObject { *self }
}
#[cfg(py_sys_config="Py_TRACE_REFS")]
pub const PyTypeObject_INIT : PyTypeObject = PyTypeObject {
_ob_next: 0 as *mut PyObject,
_ob_prev: 0 as *mut PyObject,
ob_refcnt: 1,
ob_type: 0 as *mut PyTypeObject,
ob_size: 0,
tp_name: 0 as *const c_char,
tp_basicsize: 0,
tp_itemsize: 0,
tp_dealloc: None,
tp_print: None,
tp_getattr: None,
tp_setattr: None,
tp_compare: None,
tp_repr: None,
tp_as_number: 0 as *mut PyNumberMethods,
tp_as_sequence: 0 as *mut PySequenceMethods,
tp_as_mapping: 0 as *mut PyMappingMethods,
tp_hash: None,
tp_call: None,
tp_str: None,
tp_getattro: None,
tp_setattro: None,
tp_as_buffer: 0 as *mut PyBufferProcs,
tp_flags: Py_TPFLAGS_DEFAULT,
tp_doc: 0 as *const c_char,
tp_traverse: None,
tp_clear: None,
tp_richcompare: None,
tp_weaklistoffset: 0,
tp_iter: None,
tp_iternext: None,
tp_methods: 0 as *mut PyMethodDef,
tp_members: 0 as *mut ::structmember::PyMemberDef,
tp_getset: 0 as *mut ::descrobject::PyGetSetDef,
tp_base: 0 as *mut PyTypeObject,
tp_dict: 0 as *mut PyObject,
tp_descr_get: None,
tp_descr_set: None,
tp_dictoffset: 0,
tp_init: None,
tp_alloc: None,
tp_new: None,
tp_free: None,
tp_is_gc: None,
tp_bases: 0 as *mut PyObject,
tp_mro: 0 as *mut PyObject,
tp_cache: 0 as *mut PyObject,
tp_subclasses: 0 as *mut PyObject,
tp_weaklist: 0 as *mut PyObject,
tp_del: None,
tp_version_tag: 0,
};
#[cfg(not(py_sys_config="Py_TRACE_REFS"))]
pub const PyTypeObject_INIT : PyTypeObject = PyTypeObject {
ob_refcnt: 1,
ob_type: 0 as *mut PyTypeObject,
ob_size: 0,
tp_name: 0 as *const c_char,
tp_basicsize: 0,
tp_itemsize: 0,
tp_dealloc: None,
tp_print: None,
tp_getattr: None,
tp_setattr: None,
tp_compare: None,
tp_repr: None,
tp_as_number: 0 as *mut PyNumberMethods,
tp_as_sequence: 0 as *mut PySequenceMethods,
tp_as_mapping: 0 as *mut PyMappingMethods,
tp_hash: None,
tp_call: None,
tp_str: None,
tp_getattro: None,
tp_setattro: None,
tp_as_buffer: 0 as *mut PyBufferProcs,
tp_flags: Py_TPFLAGS_DEFAULT,
tp_doc: 0 as *const c_char,
tp_traverse: None,
tp_clear: None,
tp_richcompare: None,
tp_weaklistoffset: 0,
tp_iter: None,
tp_iternext: None,
tp_methods: 0 as *mut PyMethodDef,
tp_members: 0 as *mut ::structmember::PyMemberDef,
tp_getset: 0 as *mut ::descrobject::PyGetSetDef,
tp_base: 0 as *mut PyTypeObject,
tp_dict: 0 as *mut PyObject,
tp_descr_get: None,
tp_descr_set: None,
tp_dictoffset: 0,
tp_init: None,
tp_alloc: None,
tp_new: None,
tp_free: None,
tp_is_gc: None,
tp_bases: 0 as *mut PyObject,
tp_mro: 0 as *mut PyObject,
tp_cache: 0 as *mut PyObject,
tp_subclasses: 0 as *mut PyObject,
tp_weaklist: 0 as *mut PyObject,
tp_del: None,
tp_version_tag: 0,
};
#[repr(C)]
#[derive(Copy)]
pub struct PyHeapTypeObject {

View file

@ -117,7 +117,7 @@ macro_rules! py_fn_impl {
stringify!($f),
|py| {
py_argparse_raw!(py, Some(stringify!($f)), args, kwargs,
[ $( { $pname : $ptype= $detail } )* ]
[ $( { $pname : $ptype = $detail } )* ]
{
$f(py $(, $pname )* )
})

View file

@ -175,7 +175,7 @@ pub mod _detail {
///
/// py_module_initializer!(example, initexample, PyInit_example, |py, m| {
/// try!(m.add(py, "__doc__", "Module documentation string"));
/// try!(m.add(py, "run", py_fn!(run())));
/// try!(m.add(py, "run", py_fn!(py, run())));
/// Ok(())
/// });
///

View file

@ -17,12 +17,13 @@
// DEALINGS IN THE SOFTWARE.
mod py_class;
mod slots;
#[doc(hidden)] pub mod slots;
use std::mem;
use python::{self, Python};
use libc;
use std::{mem, ptr};
use python::{self, Python, PythonObject};
use objects::{PyObject, PyType};
use err::PyResult;
use err::{self, PyResult};
use ffi;
/// Trait implemented by the types produced by the `py_class!()` macro.
@ -47,13 +48,80 @@ pub fn data_new_size<T>(base_size: usize) -> usize {
#[inline]
#[doc(hidden)]
pub unsafe fn data_get<'a, T>(_py: Python<'a>, obj: &'a PyObject, offset: usize) -> &'a T {
let ptr = (obj.as_ptr() as *mut u8).offset(offset as isize) as *const T;
let ptr = (obj.as_ptr() as *const u8).offset(offset as isize) as *const T;
&*ptr
}
#[inline]
#[doc(hidden)]
pub unsafe fn data_init<'a, T>(_py: Python<'a>, obj: &'a PyObject, offset: usize, value: T)
where T: Send + 'static
{
let ptr = (obj.as_ptr() as *mut u8).offset(offset as isize) as *mut T;
ptr::write(ptr, value)
}
#[inline]
#[doc(hidden)]
pub unsafe fn data_drop<'a, T>(_py: Python<'a>, obj: *mut ffi::PyObject, offset: usize) {
let ptr = (obj as *mut u8).offset(offset as isize) as *mut T;
ptr::drop_in_place(ptr)
}
#[inline]
#[doc(hidden)]
pub fn is_ready(_py: Python, ty: &ffi::PyTypeObject) -> bool {
(ty.tp_flags & ffi::Py_TPFLAGS_READY) != 0
}
/// A PythonObject that is usable as a base type with the `py_class!()` macro.
pub trait BaseObject : PythonObject {
/// Gets the size of the object, in bytes.
fn size() -> usize;
type InitType;
/// Allocates a new object (usually by calling ty->tp_alloc),
/// and initializes it using init_val.
/// `ty` must be derived from the Self type, and the resulting object
/// must be of type `ty`.
unsafe fn alloc(py: Python, ty: &PyType, init_val: Self::InitType) -> PyResult<PyObject>;
/// Calls the rust destructor for the object and frees the memory
/// (usually by calling ptr->ob_type->tp_free).
/// This function is used as tp_dealloc implementation.
unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject);
}
impl BaseObject for PyObject {
#[inline]
fn size() -> usize {
mem::size_of::<ffi::PyObject>()
}
type InitType = ();
unsafe fn alloc(py: Python, ty: &PyType, _init_val: ()) -> PyResult<PyObject> {
let ptr = ffi::PyType_GenericAlloc(ty.as_type_ptr(), 0);
//println!("BaseObject::alloc({:?}) = {:?}", ty.as_type_ptr(), ptr);
err::result_from_owned_ptr(py, ptr)
}
unsafe fn dealloc(_py: Python, obj: *mut ffi::PyObject) {
//println!("BaseObject::dealloc({:?})", ptr);
// Unfortunately, there is no PyType_GenericFree, so
// we have to manually un-do the work of PyType_GenericAlloc:
let ty = ffi::Py_TYPE(obj);
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(obj as *mut libc::c_void);
} else {
ffi::PyObject_Free(obj as *mut libc::c_void);
}
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}

View file

@ -20,15 +20,14 @@
# Example
```
#[macro_use] extern crate cpython;
use cpython::{Python, PyResult, PyType, PyDict};
use cpython::{Python, PyResult, PyDict};
py_class!(class MyType |py| {
data number: i32;
/*
def __new__(_cls: &PyType, arg: i32) -> PyResult<MyType> {
Ok(MyType::create_instance(py, arg))
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)
}*/
@ -44,11 +43,12 @@ fn main() {
``` */
#[macro_export]
macro_rules! py_class {
(class $name:ident |$py: ident| { $( $body:tt )* }) => (
(class $class:ident |$py: ident| { $( $body:tt )* }) => (
py_class_impl! {
$name $py
$class $py
/* info: */ {
/* size: */ ::std::mem::size_of::<$crate::PyObject>(),
/* base_type: */ $crate::PyObject,
/* size: */ <$crate::PyObject as $crate::py_class::BaseObject>::size(),
/* data: */ [ /* { offset, name, type } */ ]
// TODO: base type, documentation, ...
}
@ -70,51 +70,83 @@ macro_rules! py_class_impl {
// TT muncher macro. Results are accumulated in $info $slots $impls and $members.
// Base case: we're done munching and can start producing code:
{ $name:ident $py:ident
{ $class:ident $py:ident
/* info: */ {
$size: expr,
$base_type:ty,
$size:expr,
/* data: */ [ $( { $data_offset:expr, $data_name:ident, $data_ty:ty } )* ]
}
$slots:tt { $( $impl_tt:tt )* } $members:tt;
$slots:tt { $( $imp:item )* } $members:tt;
} => {
struct $name($crate::PyObject);
pyobject_newtype!($name, downcast using typeobject);
py_coerce_item! {
impl $name {
$($impl_tt)*
struct $class($crate::PyObject);
pyobject_newtype!($class, downcast using typeobject);
fn create_instance(py: $crate::Python $( , $data_name : $data_ty )* ) -> $name {
unimplemented!();
py_coerce_item! {
impl $crate::py_class::BaseObject for $class {
type InitType = ( $( $data_ty, )* );
#[inline]
fn size() -> usize {
$size
}
unsafe fn alloc(
py: $crate::Python,
ty: &$crate::PyType,
( $( $data_name, )* ): Self::InitType
) -> $crate::PyResult<$crate::PyObject>
{
let obj = try!(<$base_type as $crate::py_class::BaseObject>::alloc(py, ty, ()));
$( $crate::py_class::data_init::<$data_ty>(py, &obj, $data_offset, $data_name); )*
Ok(obj)
}
unsafe fn dealloc(py: $crate::Python, obj: *mut $crate::_detail::ffi::PyObject) {
$( $crate::py_class::data_drop::<$data_ty>(py, obj, $data_offset); )*
<$base_type as $crate::py_class::BaseObject>::dealloc(py, obj)
}
}
}
$($imp)*
py_coerce_item! {
impl $class {
fn create_instance(py: $crate::Python $( , $data_name : $data_ty )* ) -> $crate::PyResult<$class> {
let obj = try!(unsafe {
<$class as $crate::py_class::BaseObject>::alloc(
py, &py.get_type::<$class>(), ( $($data_name,)* )
)
});
return Ok($class(obj));
// hide statics in create_instance to avoid name conflicts
static mut type_object : $crate::_detail::ffi::PyTypeObject
= py_class_type_object_static_init!($name, $size, $slots);
= py_class_type_object_static_init!($class, $slots);
static mut init_active: bool = false;
// trait implementations that need direct access to type_object
impl $crate::PythonObjectWithTypeObject for $name {
impl $crate::PythonObjectWithTypeObject for $class {
fn type_object(py: $crate::Python) -> $crate::PyType {
unsafe {
if $crate::py_class::is_ready(py, &type_object) {
$crate::PyType::from_type_ptr(py, &mut type_object)
} else {
// automatically initialize the class on-demand
<$name as $crate::py_class::PythonObjectFromPyClassMacro>::initialize(py)
.expect("An error occurred while initializing class ")
<$class as $crate::py_class::PythonObjectFromPyClassMacro>::initialize(py)
.expect(concat!("An error occurred while initializing class ", stringify!($class)))
}
}
}
}
impl $crate::py_class::PythonObjectFromPyClassMacro for $name {
fn initialize(py: Python) -> $crate::PyResult<$crate::PyType> {
impl $crate::py_class::PythonObjectFromPyClassMacro for $class {
fn initialize(py: $crate::Python) -> $crate::PyResult<$crate::PyType> {
unsafe {
if $crate::py_class::is_ready(py, &type_object) {
return Ok($crate::PyType::from_type_ptr(py, &mut type_object));
}
assert!(!init_active,
concat!("Reentrancy detected: already initializing class ",
stringify!($name)));
stringify!($class)));
init_active = true;
let res = init(py);
init_active = false;
@ -123,8 +155,8 @@ macro_rules! py_class_impl {
}
}
unsafe fn init(py: Python) -> $crate::PyResult<$crate::PyType> {
py_class_type_object_dynamic_init!(py, type_object, $name, $size);
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 {
@ -137,16 +169,18 @@ macro_rules! py_class_impl {
};
// Data declaration
{ $name:ident $py:ident
{ $class:ident $py:ident
/* info: */ {
$base_type: ty,
$size: expr,
[ $( $data:tt )* ]
}
$slots:tt { $( $impl_tt:tt )* } $members:tt;
$slots:tt { $( $imp:item )* } $members:tt;
data $data_name:ident : $data_type:ty; $($tail:tt)*
} => { py_class_impl! {
$name $py
$class $py
/* info: */ {
$base_type,
/* size: */ $crate::py_class::data_new_size::<$data_type>($size),
/* data: */ [
$($data)*
@ -159,8 +193,9 @@ macro_rules! py_class_impl {
}
$slots
/* impl: */ {
$($impl_tt)*
pub fn $data_name<'a>(&'a self, py: $crate::Python<'a>) -> &'a $data_type {
$($imp)*
impl $class {
fn $data_name<'a>(&'a self, py: $crate::Python<'a>) -> &'a $data_type {
unsafe {
$crate::py_class::data_get::<$data_type>(
py,
@ -170,8 +205,79 @@ macro_rules! py_class_impl {
}
}
}
}
$members;
$($tail)*
}}
}};
// def __new__(cls)
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $slot_name:ident : $slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* } $members:tt;
def __new__ ($cls:ident)
-> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $slot_name : $slot_value, )*
tp_new: Some(py_class_wrap_newfunc!($class::__new__ [])),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __new__($cls: &$crate::PyType) $res_type; { $($body)* } [] }
}
$members;
$($tail)*
}};
// def __new__(cls, params)
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $slot_name:ident : $slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* } $members:tt;
def __new__ ($cls:ident, $($p:tt)+)
-> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $slot_name : $slot_value, )*
tp_new: Some(py_argparse_parse_plist_impl!{
py_class_wrap_newfunc {$class::__new__}
[] ($($p)+,)
}),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, __new__($cls: &$crate::PyType) $res_type; { $($body)* } }
[] ($($p)+,)
}
}
$members;
$($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 } )* ]
} => {
impl $class {
pub fn __new__($py: $crate::Python $( , $pname1: $ptype1 )* $( , $pname2: $ptype2 )* )
-> $res_type $body
}
}
}

View file

@ -16,17 +16,21 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use ffi;
use python::Python;
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_type_object_static_init {
($name:ident, $size:expr,
($class_name:ident,
/* slots: */ {
/* type_slots */ [ $( $slotname:ident : $slotvalue:expr, )* ]
/* type_slots */ [ $( $slot_name:ident : $slot_value:expr, )* ]
$as_number:tt
$as_sequence:tt
}) => (
$crate::_detail::ffi::PyTypeObject {
$( $slotname : $slotvalue, )*
$( $slot_name : $slot_value, )*
tp_dealloc: Some($crate::py_class::slots::tp_dealloc_callback::<$class_name>),
..
$crate::_detail::ffi::PyTypeObject_INIT
}
@ -37,13 +41,46 @@ 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, $name: ident, $size: expr) => {{
$type_object.tp_name = concat!(stringify!($name), "\0").as_ptr() as *const _;
$type_object.tp_basicsize = $size as $crate::_detail::ffi::Py_ssize_t;
($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;
}}
}
use ffi;
pub unsafe extern "C" fn tp_dealloc_callback<T>(obj: *mut ffi::PyObject)
where T: super::BaseObject
{
abort_on_panic!({
let py = Python::assume_gil_acquired();
T::dealloc(py, obj)
});
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_wrap_newfunc {
($class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_newfunc<DUMMY>(
cls: *mut $crate::_detail::ffi::PyTypeObject,
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!(), "()");
$crate::_detail::handle_callback(
LOCATION,
|py| {
py_argparse_raw!(py, Some(LOCATION), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
let cls = $crate::PyType::from_type_ptr(py, cls);
let ret = $class::$f(py, &cls $(, $pname )* );
$crate::PyDrop::release_ref(cls, py);
ret
})
})
}
wrap_newfunc::<()>
}}
}

View file

@ -39,7 +39,7 @@ use pythonrun::GILGuard;
pub struct Python<'p>(PhantomData<&'p GILGuard>);
/// Trait implemented by all Python object types.
pub trait PythonObject : ::conversion::ToPyObject + Sized + 'static {
pub trait PythonObject : ::conversion::ToPyObject + Send + Sized + 'static {
/// Casts the Python object to PyObject.
fn as_object(&self) -> &PyObject;

View file

@ -1,10 +1,11 @@
#[macro_use] extern crate cpython;
use cpython::{PyResult, Python, NoArgs, ObjectProtocol, PyDict};
use cpython::{PyResult, Python, NoArgs, ObjectProtocol};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
#[allow(dead_code)]
fn empty_class() {
py_class!(class Empty |py| { });
@ -18,9 +19,9 @@ fn empty_class() {
#[test]
fn empty_class_with_new() {
py_class!(class Empty |py| {
/*def __new__(cls) -> PyResult<Empty> {
Ok(Empty::create_instance(py))
}*/
def __new__(_cls) -> PyResult<Empty> {
Empty::create_instance(py)
}
});
let gil = Python::acquire_gil();
@ -30,6 +31,7 @@ fn empty_class_with_new() {
}
#[test]
#[allow(dead_code)]
fn data_is_dropped() {
struct MyObj {
drop_called: Arc<AtomicBool>