py_class!: Add support for class methods.

This commit is contained in:
Daniel Grunwald 2016-03-16 22:18:48 +01:00
parent f89f957dd4
commit 3826b54a30
3 changed files with 163 additions and 4 deletions

View file

@ -107,6 +107,56 @@ impl <T> TypeMember<T> for InstanceMethodDescriptor<T> where T: PythonObject {
} }
} }
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_class_method {
($py:ident, $class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_class_method<DUMMY>(
cls: *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 cls = $crate::PyObject::from_borrowed_ptr(py, cls).unchecked_cast_into::<$crate::PyType>();
let ret = $class::$f(&cls, py $(, $pname )* );
$crate::PyDrop::release_ref(cls, py);
ret
})
})
}
unsafe {
let method_def = py_method_def!(stringify!($f),
$crate::_detail::ffi::METH_CLASS,
wrap_class_method::<()>);
$crate::py_class::members::create_class_method_descriptor(method_def)
}
}}
}
pub struct ClassMethodDescriptor(*mut ffi::PyMethodDef);
#[inline]
pub unsafe fn create_class_method_descriptor(method_def: *mut ffi::PyMethodDef)
-> ClassMethodDescriptor
{
ClassMethodDescriptor(method_def)
}
impl <T> TypeMember<T> for ClassMethodDescriptor 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_NewClassMethod(ty, self.0))
}
}
#[macro_export] #[macro_export]
#[doc(hidden)] #[doc(hidden)]
macro_rules! py_class_static_method { macro_rules! py_class_static_method {
@ -129,8 +179,10 @@ macro_rules! py_class_static_method {
}) })
} }
unsafe { unsafe {
$crate::_detail::py_fn_impl($py, let method_def = py_method_def!(stringify!($f),
py_method_def!(stringify!($f), 0, wrap_static_method::<()>)) $crate::_detail::ffi::METH_STATIC,
wrap_static_method::<()>);
$crate::_detail::py_fn_impl($py, method_def)
} }
}} }}
} }

View file

@ -128,15 +128,35 @@ Declares an instance method callable from Python.
* For details on `parameter-list`, see the documentation of `py_argparse!()`. * For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`. * The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## Class methods
`@classmethod def method_name(cls, parameter-list) -> PyResult<...> { ... }
Declares a class method callable from Python.
* The first parameter is the type object of the class on which the method is called.
This may be the type object of a derived class.
* The first parameter implicitly has type `&PyType`. This type must not be explicitly specified.
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## Static methods
`@staticmethod def method_name(parameter-list) -> PyResult<...> { ... }
Declares a static method callable 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`.
## __new__ ## __new__
`def __new__(cls, parameter-list) -> PyResult<...> { ... }` `def __new__(cls, parameter-list) -> PyResult<...> { ... }`
Declares a constructor method callable from Python. 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`), * If no `__new__` method is declared, object instances can only be created from Rust (via `MyType::create_instance`),
but not from Python. 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`. This type must not be explicitly specified.
* For details on `parameter-list`, see the documentation of `py_argparse!()`. * For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`. * The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
Usually, `T` will be `MyType`. Usually, `T` will be `MyType`.
@ -993,6 +1013,49 @@ macro_rules! py_class_impl {
$($tail)* $($tail)*
}}; }};
// @classmethod def class_method(cls)
{ $class:ident $py:ident $info:tt $slots:tt
{ $( $imp:item )* }
{ $( $member_name:ident = $member_expr:expr; )* };
@classmethod def $name:ident ($cls:ident)
-> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info $slots
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, $name($cls: &$crate::PyType,) $res_type; { $($body)* } [] }
}
/* members: */ {
$( $member_name:ident = $member_expr:expr; )*
$name = py_class_class_method!{$py, $class::$name []};
};
$($tail)*
}};
// @classmethod def class_method(cls, params)
{ $class:ident $py:ident $info:tt $slots:tt
{ $( $imp:item )* }
{ $( $member_name:ident = $member_expr:expr; )* };
@classmethod def $name:ident ($cls: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($cls: &$crate::PyType,) $res_type; { $($body)* } }
[] ($($p)+,)
}
}
/* members: */ {
$( $member_name:ident = $member_expr:expr; )*
$name = py_argparse_parse_plist_impl!{
py_class_class_method {$py, $class::$name}
[] ($($p)+,)
};
};
$($tail)*
}};
// @staticmethod def static_method(params) // @staticmethod def static_method(params)
{ $class:ident $py:ident $info:tt $slots:tt { $class:ident $py:ident $info:tt $slots:tt
{ $( $imp:item )* } { $( $imp:item )* }

View file

@ -137,7 +137,50 @@ fn instance_method_with_args() {
py.run("assert obj.method(multiplier=6) == 42", None, Some(&d)).unwrap(); py.run("assert obj.method(multiplier=6) == 42", None, Some(&d)).unwrap();
} }
py_class!(class ClassMethod |py| {
def __new__(cls) -> PyResult<ClassMethod> {
ClassMethod::create_instance(py)
}
@classmethod
def method(cls) -> PyResult<String> {
Ok(format!("{}.method()!", cls.name(py)))
}
});
#[test]
fn class_method() {
let gil = Python::acquire_gil();
let py = gil.python();
let d = PyDict::new(py);
d.set_item(py, "C", py.get_type::<ClassMethod>()).unwrap();
py.run("assert C.method() == 'ClassMethod.method()!'", None, Some(&d)).unwrap();
py.run("assert C().method() == 'ClassMethod.method()!'", None, Some(&d)).unwrap();
}
py_class!(class ClassMethodWithArgs |py| {
@classmethod
def method(cls, input: &str) -> PyResult<String> {
Ok(format!("{}.method({})", cls.name(py), input))
}
});
#[test]
fn class_method_with_args() {
let gil = Python::acquire_gil();
let py = gil.python();
let d = PyDict::new(py);
d.set_item(py, "C", py.get_type::<ClassMethodWithArgs>()).unwrap();
py.run("assert C.method('abc') == 'ClassMethodWithArgs.method(abc)'", None, Some(&d)).unwrap();
}
py_class!(class StaticMethod |py| { py_class!(class StaticMethod |py| {
def __new__(cls) -> PyResult<StaticMethod> {
StaticMethod::create_instance(py)
}
@staticmethod @staticmethod
def method() -> PyResult<&'static str> { def method() -> PyResult<&'static str> {
Ok("StaticMethod.method()!") Ok("StaticMethod.method()!")
@ -153,6 +196,7 @@ fn static_method() {
let d = PyDict::new(py); let d = PyDict::new(py);
d.set_item(py, "C", py.get_type::<StaticMethod>()).unwrap(); d.set_item(py, "C", py.get_type::<StaticMethod>()).unwrap();
py.run("assert C.method() == 'StaticMethod.method()!'", None, Some(&d)).unwrap(); py.run("assert C.method() == 'StaticMethod.method()!'", None, Some(&d)).unwrap();
py.run("assert C().method() == 'StaticMethod.method()!'", None, Some(&d)).unwrap();
} }
py_class!(class StaticMethodWithArgs |py| { py_class!(class StaticMethodWithArgs |py| {