py_class!: add __getitem__
This commit is contained in:
parent
713a5c53d3
commit
3de859373c
|
@ -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>`
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)*
|
||||
|
|
|
@ -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)*
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue