py_class!: add __call__ slot

This commit is contained in:
Daniel Grunwald 2016-05-06 22:32:46 +02:00
parent 78f673893d
commit 318d9a9ef3
6 changed files with 211 additions and 92 deletions

View File

@ -285,7 +285,7 @@ TODO: implement support for `__cmp__`, `__lt__`, `__le__`, `__gt__`, `__ge__`, `
Objects that compare equal must have the same hash value.
The return type must be `PyResult<T>` where `T` is one of Rust's primitive integer types.
## Not sure where this belongs to in the documentation
## Other Special Methods
* `def __bool__(&self) -> PyResult<bool>`
@ -294,6 +294,12 @@ TODO: implement support for `__cmp__`, `__lt__`, `__le__`, `__gt__`, `__ge__`, `
Note that `py_class!` always expects this member to be called `__bool__`,
even on Python 2.7 where the Python spelling was `__nonzero__`.
* `def __call__(&self, parameter-list) -> PyResult<impl ToPyObject>`
For details on `parameter-list`, see the documentation of `py_argparse!()`.
The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
*/
#[macro_export]
macro_rules! py_class {

View File

@ -438,50 +438,36 @@ def traverse_and_clear():
$($tail)*
}};''')
instance_method = '''
// 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 = $member_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)* } }
def generate_instance_method(special_name=None, decoration='',
slot=None, add_member=False, value_macro=None, value_args=None):
name_pattern = special_name or '$name:ident'
name_use = special_name or '$name'
def impl(with_params):
if with_params:
param_pattern = ', $($p:tt)+'
impl = '''py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, %s(&$slf,) $res_type; { $($body)* } }
[] ($($p)+,)
}
}
/* members: */ {
$( $member_name = $member_expr; )*
$name = py_argparse_parse_plist_impl!{
py_class_instance_method {$py, $class::$name}
[] ($($p)+,)
};
};
$($tail)*
}};
'''
}''' % name_use
value = 'py_argparse_parse_plist_impl!{%s {%s} [] ($($p)+,)}' \
% (value_macro, value_args)
else:
param_pattern = ''
impl = 'py_class_impl_item! { $class, $py, %s(&$slf,) $res_type; { $($body)* } [] }' \
% name_use
value = '%s!{%s []}' % (value_macro, value_args)
pattern = '%s def %s (&$slf:ident%s) -> $res_type:ty { $( $body:tt )* }' \
% (decoration, name_pattern, param_pattern)
slots = []
if slot is not None:
slots.append((slot, value))
members = []
if add_member:
members.append((name_use, value))
generate_case(pattern, new_impl=impl, new_slots=slots, new_members=members)
impl(False) # without parameters
impl(True) # with parameters
static_method = '''
// @staticmethod def static_method(params)
@ -577,6 +563,14 @@ def unary_operator(special_name, slot,
# when using the wrong method signature
error('Invalid signature for unary operator %s' % special_name)(special_name)
@special_method
def call_operator(special_name, slot):
generate_instance_method(
special_name=special_name,
slot=slot,
value_macro='py_class_call_slot',
value_args='$class::%s' % special_name)
special_names = {
'__init__': error('__init__ is not supported by py_class!; use __new__ instead.'),
'__new__': special_class_method(
@ -610,7 +604,7 @@ special_names = {
'__setattr__': unimplemented(),
'__delattr__': unimplemented(),
'__dir__': unimplemented(),
# Implementing Descriptors
'__get__': unimplemented(),
'__set__': unimplemented(),
@ -621,8 +615,8 @@ special_names = {
'__subclasscheck__': unimplemented(),
# Emulating callable objects
'__call__': unimplemented(),
'__call__': call_operator('tp_call'),
# Emulating container types
'__len__': unary_operator('sq_length',
res_ffi_type='$crate::_detail::ffi::Py_ssize_t',
@ -726,7 +720,10 @@ def main():
traverse_and_clear()
for name, f in sorted(special_names.items()):
f(name)
print(instance_method)
generate_instance_method(
add_member=True,
value_macro='py_class_instance_method',
value_args='$py, $class::$name')
generate_class_method(decoration='@classmethod',
add_member=True,
value_macro='py_class_class_method',

View File

@ -365,12 +365,55 @@ macro_rules! py_class_impl {
} => {
py_error! { "Invalid signature for unary operator __bool__" }
};
// def __call__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __call__ $($tail:tt)*
} => {
py_error! { "__call__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __call__ (&$slf:ident) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_call: py_class_call_slot!{$class::__call__ []},
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __call__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __call__ (&$slf:ident, $($p:tt)+) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_call: py_argparse_parse_plist_impl!{py_class_call_slot {$class::__call__} [] ($($p)+,)},
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, __call__(&$slf,) $res_type; { $($body)* } }
[] ($($p)+,)
}
}
$members; $($tail)*
}};
// def __cmp__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __cmp__ $($tail:tt)*
@ -1086,13 +1129,10 @@ macro_rules! py_class_impl {
} => {
py_error! { "__xor__ is not supported by py_class! yet." }
};
// 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)*
def $name:ident (&$slf:ident) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info $slots
/* impl: */ {
@ -1102,15 +1142,12 @@ macro_rules! py_class_impl {
/* members: */ {
$( $member_name = $member_expr; )*
$name = py_class_instance_method!{$py, $class::$name []};
};
$($tail)*
}; $($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)*
def $name:ident (&$slf:ident, $($p:tt)+) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info $slots
/* impl: */ {
@ -1122,14 +1159,9 @@ macro_rules! py_class_impl {
}
/* members: */ {
$( $member_name = $member_expr; )*
$name = py_argparse_parse_plist_impl!{
py_class_instance_method {$py, $class::$name}
[] ($($p)+,)
};
};
$($tail)*
$name = py_argparse_parse_plist_impl!{py_class_instance_method {$py, $class::$name} [] ($($p)+,)};
}; $($tail)*
}};
{ $class:ident $py:ident $info:tt $slots:tt
{ $( $imp:item )* }
{ $( $member_name:ident = $member_expr:expr; )* };

View File

@ -365,12 +365,55 @@ macro_rules! py_class_impl {
} => {
py_error! { "Invalid signature for unary operator __bool__" }
};
// def __call__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __call__ $($tail:tt)*
} => {
py_error! { "__call__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __call__ (&$slf:ident) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_call: py_class_call_slot!{$class::__call__ []},
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __call__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __call__ (&$slf:ident, $($p:tt)+) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_call: py_argparse_parse_plist_impl!{py_class_call_slot {$class::__call__} [] ($($p)+,)},
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_argparse_parse_plist_impl!{
py_class_impl_item { $class, $py, __call__(&$slf,) $res_type; { $($body)* } }
[] ($($p)+,)
}
}
$members; $($tail)*
}};
// def __cmp__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __cmp__ $($tail:tt)*
@ -1086,13 +1129,10 @@ macro_rules! py_class_impl {
} => {
py_error! { "__xor__ is not supported by py_class! yet." }
};
// 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)*
def $name:ident (&$slf:ident) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info $slots
/* impl: */ {
@ -1102,15 +1142,12 @@ macro_rules! py_class_impl {
/* members: */ {
$( $member_name = $member_expr; )*
$name = py_class_instance_method!{$py, $class::$name []};
};
$($tail)*
}; $($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)*
def $name:ident (&$slf:ident, $($p:tt)+) -> $res_type:ty { $( $body:tt )* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info $slots
/* impl: */ {
@ -1122,14 +1159,9 @@ macro_rules! py_class_impl {
}
/* members: */ {
$( $member_name = $member_expr; )*
$name = py_argparse_parse_plist_impl!{
py_class_instance_method {$py, $class::$name}
[] ($($p)+,)
};
};
$($tail)*
$name = py_argparse_parse_plist_impl!{py_class_instance_method {$py, $class::$name} [] ($($p)+,)};
}; $($tail)*
}};
{ $class:ident $py:ident $info:tt $slots:tt
{ $( $imp:item )* }
{ $( $member_name:ident = $member_expr:expr; )* };

View File

@ -285,3 +285,33 @@ impl CallbackConverter<bool> for BoolConverter {
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_call_slot {
($class:ident :: $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]) => {{
unsafe extern "C" fn wrap_call<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, $crate::_detail::PyObjectCallbackConverter,
|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
})
})
}
Some(wrap_call::<()>)
}}
}

View File

@ -416,4 +416,26 @@ fn comparisons() {
}
py_class!(class Callable |py| {
def __call__(&self, arg: i32) -> PyResult<i32> {
Ok(arg * 6)
}
});
#[test]
fn callable() {
let gil = Python::acquire_gil();
let py = gil.python();
let c = Callable::create_instance(py).unwrap();
py_assert!(py, c, "callable(c)");
py_assert!(py, c, "c(7) == 42");
let nc = Comparisons::create_instance(py, 0).unwrap();
py_assert!(py, nc, "not callable(nc)");
}