py_class!: Add support for class methods.
This commit is contained in:
parent
f89f957dd4
commit
3826b54a30
|
@ -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]
|
||||
#[doc(hidden)]
|
||||
macro_rules! py_class_static_method {
|
||||
|
@ -129,8 +179,10 @@ macro_rules! py_class_static_method {
|
|||
})
|
||||
}
|
||||
unsafe {
|
||||
$crate::_detail::py_fn_impl($py,
|
||||
py_method_def!(stringify!($f), 0, wrap_static_method::<()>))
|
||||
let method_def = py_method_def!(stringify!($f),
|
||||
$crate::_detail::ffi::METH_STATIC,
|
||||
wrap_static_method::<()>);
|
||||
$crate::_detail::py_fn_impl($py, method_def)
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
|
|
@ -128,15 +128,35 @@ Declares an instance 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`.
|
||||
|
||||
## 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__
|
||||
`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.
|
||||
* 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!()`.
|
||||
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
|
||||
Usually, `T` will be `MyType`.
|
||||
|
@ -993,6 +1013,49 @@ macro_rules! py_class_impl {
|
|||
$($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)
|
||||
{ $class:ident $py:ident $info:tt $slots:tt
|
||||
{ $( $imp:item )* }
|
||||
|
|
|
@ -137,7 +137,50 @@ fn instance_method_with_args() {
|
|||
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| {
|
||||
def __new__(cls) -> PyResult<StaticMethod> {
|
||||
StaticMethod::create_instance(py)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def method() -> PyResult<&'static str> {
|
||||
Ok("StaticMethod.method()!")
|
||||
|
@ -153,6 +196,7 @@ fn static_method() {
|
|||
let d = PyDict::new(py);
|
||||
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_class!(class StaticMethodWithArgs |py| {
|
||||
|
|
Loading…
Reference in New Issue