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]
|
#[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)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 )* }
|
||||||
|
|
|
@ -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| {
|
||||||
|
|
Loading…
Reference in a new issue