py_class!: add __getitem__

This commit is contained in:
Daniel Grunwald 2016-05-07 00:04:18 +02:00
parent 713a5c53d3
commit 3de859373c
6 changed files with 172 additions and 41 deletions

View File

@ -298,6 +298,10 @@ TODO: implement support for `__cmp__`, `__lt__`, `__le__`, `__gt__`, `__ge__`, `
`__length_hint__` is new in Python 3.4; older versions will ignore the method.
* `def __getitem__(&self, key: impl ExtractPyObject) -> PyResult<impl ToPyObject>`
Called by the Python subscript operator `self[key]`.
## Other Special Methods
* `def __bool__(&self) -> PyResult<bool>`

View File

@ -542,28 +542,39 @@ def normal_method(special_name):
def special_class_method(special_name, *args, **kwargs):
generate_class_method(special_name=special_name, *args, **kwargs)
Argument = namedtuple('Argument', ['name', 'default_type'])
@special_method
def unary_operator(special_name, slot,
def operator(special_name, slot,
args=(),
res_type='PyObject',
res_conv=None,
res_ffi_type='*mut $crate::_detail::ffi::PyObject'
res_ffi_type='*mut $crate::_detail::ffi::PyObject',
additional_slots=()
):
if res_conv is None:
if res_type == 'PyObject':
res_conv = '$crate::_detail::PyObjectCallbackConverter'
else:
res_conv = '$crate::_detail::PythonObjectCallbackConverter::<$crate::%s>(::std::marker::PhantomData)' % res_type
new_slots = [(slot, 'py_class_unary_slot!($class::%s, %s, %s)'
% (special_name, res_ffi_type, res_conv))]
if slot == 'sq_length':
# __len__ must be saved to two slots
# Use PySequence_Size to forward mp_length calls to sq_length.
new_slots.append(('mp_length', 'Some($crate::_detail::ffi::PySequence_Size)'))
arg_pattern = ''
param_list = []
for arg in args:
arg_pattern += ', ${0}:ident : ${0}_type:ty'.format(arg.name)
param_list.append('{{ ${0} : ${0}_type = {{}} }}'.format(arg.name))
if len(args) == 0:
new_slots = [(slot, 'py_class_unary_slot!($class::%s, %s, %s)'
% (special_name, res_ffi_type, res_conv))]
elif len(args) == 1:
new_slots = [(slot, 'py_class_binary_slot!($class::%s, $%s_type, %s, %s)'
% (special_name, arg.name, res_ffi_type, res_conv))]
else:
raise ValueError('Unsupported argument count')
generate_case(
pattern='def %s(&$slf:ident) -> $res_type:ty { $($body:tt)* }' % special_name,
new_impl='py_class_impl_item! { $class, $py, %s(&$slf,) $res_type; { $($body)* } [] }'
% special_name,
new_slots=new_slots
pattern='def %s(&$slf:ident%s) -> $res_type:ty { $($body:tt)* }' % (special_name, arg_pattern),
new_impl='py_class_impl_item! { $class, $py, %s(&$slf,) $res_type; { $($body)* } [%s] }'
% (special_name, ' '.join(param_list)),
new_slots=new_slots + list(additional_slots)
)
# Generate fall-back matcher that produces an error
# when using the wrong method signature
@ -584,8 +595,8 @@ special_names = {
value_macro='py_class_wrap_newfunc',
value_args='$class::__new__'),
'__del__': error('__del__ is not supported by py_class!; Use a data member with a Drop impl instead.'),
'__repr__': unary_operator('tp_repr', res_type="PyString"),
'__str__': unary_operator('tp_str', res_type="PyString"),
'__repr__': operator('tp_repr', res_type="PyString"),
'__str__': operator('tp_str', res_type="PyString"),
'__unicode__': normal_method(),
'__bytes__': normal_method(),
'__format__': normal_method(),
@ -597,11 +608,11 @@ special_names = {
'__eq__': unimplemented(),
'__ne__': unimplemented(),
'__cmp__': unimplemented(),
'__hash__': unary_operator('tp_hash',
'__hash__': operator('tp_hash',
res_conv='$crate::py_class::slots::HashConverter',
res_ffi_type='$crate::Py_hash_t'),
'__nonzero__': error('__nonzero__ is not supported by py_class!; use the Python 3 spelling __bool__ instead.'),
'__bool__': unary_operator('nb_nonzero' if PY2 else 'nb_bool',
'__bool__': operator('nb_nonzero' if PY2 else 'nb_bool',
res_conv='$crate::py_class::slots::BoolConverter',
res_ffi_type='$crate::_detail::libc::c_int'),
# Customizing attribute access
@ -624,16 +635,23 @@ special_names = {
'__call__': call_operator('tp_call'),
# Emulating container types
'__len__': unary_operator('sq_length',
'__len__': operator('sq_length',
res_ffi_type='$crate::_detail::ffi::Py_ssize_t',
res_conv='$crate::py_class::slots::LenResultConverter'),
res_conv='$crate::py_class::slots::LenResultConverter',
additional_slots=[
# Use PySequence_Size to forward mp_length calls to sq_length.
('mp_length', 'Some($crate::_detail::ffi::PySequence_Size)')
]),
'__length_hint__': normal_method(),
'__getitem__': unimplemented(),
'__missing__': unimplemented(),
'__getitem__': operator('mp_subscript', args=[Argument('x', '&PyObject')],
additional_slots=[
('sq_item', 'Some($crate::py_class::slots::sq_item)')
]),
'__missing__': normal_method(),
'__setitem__': unimplemented(),
'__delitem__': unimplemented(),
'__iter__': unary_operator('tp_iter'),
'__next__': unary_operator('tp_iternext',
'__iter__': operator('tp_iter'),
'__next__': operator('tp_iternext',
res_conv='$crate::py_class::slots::IterNextResultConverter'),
'__reversed__': unimplemented(),
'__contains__': unimplemented(),

View File

@ -534,11 +534,39 @@ macro_rules! py_class_impl {
} => {
py_error! { "__getattribute__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
$type_slots:tt $as_number:tt
/* as_sequence */ [ $( $sq_slot_name:ident : $sq_slot_value:expr, )* ]
/* as_mapping */ [ $( $mp_slot_name:ident : $mp_slot_value:expr, )* ]
}
{ $( $imp:item )* }
$members:tt;
def __getitem__(&$slf:ident, $x:ident : $x_type:ty) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
$type_slots $as_number
/* as_sequence */ [
$( $sq_slot_name : $sq_slot_value, )*
sq_item: Some($crate::py_class::slots::sq_item),
]
/* as_mapping */ [
$( $mp_slot_name : $mp_slot_value, )*
mp_subscript: py_class_binary_slot!($class::__getitem__, $x_type, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PyObjectCallbackConverter),
]
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __getitem__(&$slf,) $res_type; { $($body)* } [{ $x : $x_type = {} }] }
}
$members; $($tail)*
}};
// def __getitem__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __getitem__ $($tail:tt)*
} => {
py_error! { "__getitem__ is not supported by py_class! yet." }
py_error! { "Invalid signature for unary operator __getitem__" }
};
// def __gt__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
@ -788,12 +816,6 @@ macro_rules! py_class_impl {
} => {
py_error! { "__matmul__ is not supported by py_class! yet." }
};
// def __missing__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __missing__ $($tail:tt)*
} => {
py_error! { "__missing__ is not supported by py_class! yet." }
};
// def __mod__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __mod__ $($tail:tt)*

View File

@ -534,11 +534,39 @@ macro_rules! py_class_impl {
} => {
py_error! { "__getattribute__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
$type_slots:tt $as_number:tt
/* as_sequence */ [ $( $sq_slot_name:ident : $sq_slot_value:expr, )* ]
/* as_mapping */ [ $( $mp_slot_name:ident : $mp_slot_value:expr, )* ]
}
{ $( $imp:item )* }
$members:tt;
def __getitem__(&$slf:ident, $x:ident : $x_type:ty) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
$type_slots $as_number
/* as_sequence */ [
$( $sq_slot_name : $sq_slot_value, )*
sq_item: Some($crate::py_class::slots::sq_item),
]
/* as_mapping */ [
$( $mp_slot_name : $mp_slot_value, )*
mp_subscript: py_class_binary_slot!($class::__getitem__, $x_type, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PyObjectCallbackConverter),
]
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __getitem__(&$slf,) $res_type; { $($body)* } [{ $x : $x_type = {} }] }
}
$members; $($tail)*
}};
// def __getitem__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __getitem__ $($tail:tt)*
} => {
py_error! { "__getitem__ is not supported by py_class! yet." }
py_error! { "Invalid signature for unary operator __getitem__" }
};
// def __gt__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
@ -788,12 +816,6 @@ macro_rules! py_class_impl {
} => {
py_error! { "__matmul__ is not supported by py_class! yet." }
};
// def __missing__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __missing__ $($tail:tt)*
} => {
py_error! { "__missing__ is not supported by py_class! yet." }
};
// def __mod__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __mod__ $($tail:tt)*

View File

@ -197,6 +197,39 @@ macro_rules! py_class_unary_slot {
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_class_binary_slot {
($class:ident :: $f:ident, $arg_type:ty, $res_type:ty, $conv:expr) => {{
unsafe extern "C" fn wrap_unary<DUMMY>(
slf: *mut $crate::_detail::ffi::PyObject,
arg: *mut $crate::_detail::ffi::PyObject)
-> $res_type
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");
$crate::_detail::handle_callback(
LOCATION, $conv,
|py| {
let slf = $crate::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<$class>();
let arg = $crate::PyObject::from_borrowed_ptr(py, arg);
let ret = match <$arg_type as $crate::ExtractPyObject>::prepare_extract(py, &arg) {
Ok(prepared) => {
match <$arg_type as $crate::ExtractPyObject>::extract(py, &prepared) {
Ok(arg) => slf.$f(py, arg),
Err(e) => Err(e)
}
},
Err(e) => Err(e)
};
$crate::PyDrop::release_ref(arg, py);
$crate::PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap_unary::<()>)
}}
}
pub struct LenResultConverter;
impl CallbackConverter<usize> for LenResultConverter {
@ -332,4 +365,14 @@ macro_rules! py_class_call_slot {
}}
}
/// Used as implementation in the `sq_item` slot to forward calls to the `mp_subscript` slot.
pub unsafe extern "C" fn sq_item(obj: *mut ffi::PyObject, index: ffi::Py_ssize_t) -> *mut ffi::PyObject {
let arg = ffi::PyLong_FromSsize_t(index);
if arg.is_null() {
return arg;
}
let ret = ffi::PyObject_GetItem(obj, arg);
ffi::Py_DECREF(arg);
ret
}

View File

@ -3,7 +3,7 @@
#[macro_use] extern crate cpython;
use cpython::{PyObject, PythonObject, PyDrop, PyClone, PyResult, Python, NoArgs, ObjectProtocol,
PyDict, PyBytes, PyUnicode, exc};
PyDict, PyBytes, PyUnicode, PyErr, exc};
use std::{mem, isize, iter};
use std::cell::RefCell;
use std::sync::Arc;
@ -420,13 +420,38 @@ fn comparisons() {
}
py_class!(class Sequence |py| {
def __len__(&self) -> PyResult<usize> {
Ok(5)
}
def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
if let Ok(index) = key.extract::<i32>(py) {
if index == 5 {
return Err(PyErr::new::<exc::IndexError, NoArgs>(py, NoArgs));
}
}
Ok(key)
}
});
#[test]
fn sequence() {
let gil = Python::acquire_gil();
let py = gil.python();
let c = Sequence::create_instance(py).unwrap();
py_assert!(py, c, "list(c) == [0, 1, 2, 3, 4]");
py_assert!(py, c, "c['abc'] == 'abc'");
}
py_class!(class Callable |py| {
def __call__(&self, arg: i32) -> PyResult<i32> {
Ok(arg * 6)
}
});
#[test]
fn callable() {
let gil = Python::acquire_gil();
@ -440,6 +465,3 @@ fn callable() {
py_assert!(py, nc, "not callable(nc)");
}