Add back PyIterator, PyListIterator etc.

This commit is contained in:
Daniel Grunwald 2015-10-29 02:36:50 +01:00
parent 0a270a0583
commit 32086a0ef5
12 changed files with 235 additions and 174 deletions

View File

@ -136,7 +136,7 @@ mod sysmodule; // TODO supports PEP-384 only; needs adjustment for Python 3.3 an
mod intrcheck; // TODO supports PEP-384 only; needs adjustment for Python 3.3 and 3.5
mod import; // TODO supports PEP-384 only; needs adjustment for Python 3.3 and 3.5
mod objectabstract; // TODO supports PEP-384 only; needs adjustment for Python 3.3 and 3.5
mod objectabstract;
mod bltinmodule; // TODO supports PEP-384 only; needs adjustment for Python 3.3 and 3.5
#[cfg(Py_LIMITED_API)] mod code {}

View File

@ -2,6 +2,7 @@ use libc::{c_char, c_int, c_long};
use pyport::Py_ssize_t;
use object::PyObject;
use moduleobject::PyModuleDef;
#[cfg(Py_3_5)]
use methodobject::PyMethodDef;
extern "C" {

View File

@ -570,6 +570,10 @@ extern "C" {
arg2: *mut Struct__Py_Identifier)
-> c_int;*/
pub fn PyObject_SelfIter(arg1: *mut PyObject) -> *mut PyObject;
#[cfg(not(Py_LIMITED_API))]
pub fn _PyObject_NextNotImplemented(arg1: *mut PyObject) -> *mut PyObject;
pub fn PyObject_GenericGetAttr(arg1: *mut PyObject, arg2: *mut PyObject)
-> *mut PyObject;
pub fn PyObject_GenericSetAttr(arg1: *mut PyObject, arg2: *mut PyObject,

View File

@ -42,6 +42,10 @@ pub unsafe fn PyObject_Length(o: *mut PyObject) -> Py_ssize_t {
}
extern "C" {
#[cfg(all(not(Py_LIMITED_API), Py_3_4))]
pub fn PyObject_LengthHint(o: *mut PyObject, arg1: Py_ssize_t)
-> Py_ssize_t;
pub fn PyObject_GetItem(o: *mut PyObject, key: *mut PyObject)
-> *mut PyObject;
pub fn PyObject_SetItem(o: *mut PyObject, key: *mut PyObject,
@ -64,16 +68,57 @@ extern "C" {
buffer: *mut *mut c_void,
buffer_len: *mut Py_ssize_t)
-> c_int;
}
#[cfg(not(Py_LIMITED_API))]
#[inline]
pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int {
let tp_as_buffer = (*(*o).ob_type).tp_as_buffer;
(!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int
}
#[cfg(not(Py_LIMITED_API))]
extern "C" {
pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer,
flags: c_int) -> c_int;
pub fn PyBuffer_GetPointer(view: *mut Py_buffer, indices: *mut Py_ssize_t)
-> *mut c_void;
pub fn PyBuffer_ToContiguous(buf: *mut c_void,
view: *mut Py_buffer, len: Py_ssize_t,
order: c_char) -> c_int;
pub fn PyBuffer_FromContiguous(view: *mut Py_buffer,
buf: *mut c_void, len: Py_ssize_t,
order: c_char) -> c_int;
pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject)
-> c_int;
pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char)
-> c_int;
pub fn PyBuffer_FillContiguousStrides(ndims: c_int,
shape: *mut Py_ssize_t,
strides: *mut Py_ssize_t,
itemsize: c_int,
fort: c_char) -> ();
pub fn PyBuffer_FillInfo(view: *mut Py_buffer, o: *mut PyObject,
buf: *mut c_void, len: Py_ssize_t,
readonly: c_int, flags: c_int)
-> c_int;
pub fn PyBuffer_Release(view: *mut Py_buffer) -> ();
}
extern "C" {
pub fn PyObject_Format(obj: *mut PyObject, format_spec: *mut PyObject)
-> *mut PyObject;
pub fn PyObject_GetIter(arg1: *mut PyObject) -> *mut PyObject;
}
/* not available in limited ABI
#define PyIter_Check(obj) \
((obj)->ob_type->tp_iternext != NULL && \
(obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)
*/
#[cfg(not(Py_LIMITED_API))]
#[inline]
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
(match (*(*o).ob_type).tp_iternext {
Some(tp_iternext) => tp_iternext as *const c_void != ::object::_PyObject_NextNotImplemented as *const c_void,
None => false
}) as c_int
}
extern "C" {
pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject;
@ -85,6 +130,9 @@ extern "C" {
-> *mut PyObject;
pub fn PyNumber_Multiply(o1: *mut PyObject, o2: *mut PyObject)
-> *mut PyObject;
#[cfg(Py_3_5)]
pub fn PyNumber_MatrixMultiply(o1: *mut PyObject, o2: *mut PyObject)
-> *mut PyObject;
pub fn PyNumber_FloorDivide(o1: *mut PyObject, o2: *mut PyObject)
-> *mut PyObject;
pub fn PyNumber_TrueDivide(o1: *mut PyObject, o2: *mut PyObject)
@ -110,11 +158,12 @@ extern "C" {
pub fn PyNumber_Or(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject;
}
/*
#define PyIndex_Check(obj) \
((obj)->ob_type->tp_as_number != NULL && \
(obj)->ob_type->tp_as_number->nb_index != NULL)
*/
#[cfg(not(Py_LIMITED_API))]
#[inline]
pub unsafe fn PyIndex_Check(o: *mut PyObject) -> c_int {
let tp_as_number = (*(*o).ob_type).tp_as_number;
(!tp_as_number.is_null() && (*tp_as_number).nb_index.is_some()) as c_int
}
extern "C" {
pub fn PyNumber_Index(o: *mut PyObject) -> *mut PyObject;
@ -128,6 +177,9 @@ extern "C" {
-> *mut PyObject;
pub fn PyNumber_InPlaceMultiply(o1: *mut PyObject, o2: *mut PyObject)
-> *mut PyObject;
#[cfg(Py_3_5)]
pub fn PyNumber_InPlaceMatrixMultiply(o1: *mut PyObject, o2: *mut PyObject)
-> *mut PyObject;
pub fn PyNumber_InPlaceFloorDivide(o1: *mut PyObject, o2: *mut PyObject)
-> *mut PyObject;
pub fn PyNumber_InPlaceTrueDivide(o1: *mut PyObject, o2: *mut PyObject)
@ -180,6 +232,7 @@ extern "C" {
pub fn PySequence_List(o: *mut PyObject) -> *mut PyObject;
pub fn PySequence_Fast(o: *mut PyObject, m: *const c_char)
-> *mut PyObject;
// TODO: PySequence_Fast macros
pub fn PySequence_Count(o: *mut PyObject, value: *mut PyObject)
-> Py_ssize_t;
pub fn PySequence_Contains(seq: *mut PyObject, ob: *mut PyObject)

View File

@ -209,16 +209,16 @@ pub trait ObjectProtocol : PythonObject {
})
}
/* TODO /// Takes an object and returns an iterator for it.
/// Takes an object and returns an iterator for it.
/// This is typically a new iterator but if the argument
/// is an iterator, this returns itself.
#[cfg(feature="python27-sys")]
#[inline]
fn iter(&self, py: Python) -> PyResult<::objects::PyIterator<'p>> {
unsafe {
err::result_cast_from_owned_ptr(self.python(), ffi::PyObject_GetIter(self.as_ptr()))
}
}*/
fn iter<'p>(&self, py: Python<'p>) -> PyResult<::objects::PyIterator<'p>> {
let obj = try!(unsafe {
err::result_from_owned_ptr(py, ffi::PyObject_GetIter(self.as_ptr()))
});
Ok(try!(::objects::PyIterator::from_object(py, obj)))
}
}
impl ObjectProtocol for PyObject {}

View File

@ -48,5 +48,26 @@ extract!(obj to bool; py => {
Ok(try!(obj.cast_as::<PyBool>(py)).is_true())
});
// TODO: mod tests
#[cfg(test)]
mod test {
use python::{Python, PythonObject};
use conversion::ToPyObject;
#[test]
fn test_true() {
let gil = Python::acquire_gil();
let py = gil.python();
assert!(py.True().is_true());
assert_eq!(true, py.True().as_object().extract(py).unwrap());
assert!(true.to_py_object(py).as_object() == py.True().as_object());
}
#[test]
fn test_false() {
let gil = Python::acquire_gil();
let py = gil.python();
assert!(!py.False().is_true());
assert_eq!(false, py.False().as_object().extract(py).unwrap());
assert!(false.to_py_object(py).as_object() == py.False().as_object());
}
}

View File

@ -158,7 +158,6 @@ impl <K, V> ToPyObject for collections::BTreeMap<K, V>
#[cfg(test)]
mod test {
use std;
use python::{Python, PythonObject};
use conversion::ToPyObject;
use objects::{PyDict, PyTuple};
@ -248,9 +247,8 @@ mod test {
assert_eq!(32i32, *v.get(&7i32).unwrap()); // not updated!
}
/*
#[test]
TODO fn test_items_list() {
fn test_items_list() {
let gil = Python::acquire_gil();
let py = gil.python();
let mut v = HashMap::new();
@ -261,15 +259,14 @@ TODO fn test_items_list() {
// Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
let mut key_sum = 0;
let mut value_sum = 0;
for el in dict.items_list(py) {
let tuple = el.cast_into::<PyTuple>().unwrap();
key_sum += tuple.get_item(0).extract::<i32>().unwrap();
value_sum += tuple.get_item(1).extract::<i32>().unwrap();
for el in dict.items_list(py).iter(py) {
let tuple = el.cast_into::<PyTuple>(py).unwrap();
key_sum += tuple.get_item(py, 0).extract::<i32>(py).unwrap();
value_sum += tuple.get_item(py, 1).extract::<i32>(py).unwrap();
}
assert_eq!(7 + 8 + 9, key_sum);
assert_eq!(32 + 42 + 123, value_sum);
}
*/
#[test]
fn test_items() {

View File

@ -16,29 +16,82 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use python::{Python, PythonObject, ToPythonPointer};
use python::{Python, PythonObject, ToPythonPointer, PythonObjectDowncastError};
use conversion::ToPyObject;
use objects::PyObject;
use err::{PyErr, PyResult};
use ffi;
pub struct PyIterator(PyObject);
/// A python iterator object.
///
/// Unlike other python objects, this class includes a `Python<'p>` token
/// so that PyIterator can implement the rust `Iterator` trait.
pub struct PyIterator<'p> {
py: Python<'p>,
iter: PyObject,
}
pyobject_newtype!(PyIterator, PyIter_Check);
impl <'p> PyIterator<'p> {
/// Constructs a PyIterator from a Python iterator object.
pub fn from_object(py: Python<'p>, obj: PyObject) -> Result<PyIterator<'p>, PythonObjectDowncastError<'p>> {
if unsafe { ffi::PyIter_Check(obj.as_ptr()) != 0 } {
Ok(PyIterator { py: py, iter: obj })
} else {
Err(PythonObjectDowncastError(py))
}
}
/// Gets the Python iterator object.
#[inline]
pub fn as_object(&self) -> &PyObject {
&self.iter
}
/// Gets the Python iterator object.
#[inline]
pub fn into_object(self) -> PyObject {
self.iter
}
}
impl <'p> Iterator for PyIterator<'p> {
type Item = PyResult<PyObject>;
impl PyIterator {
/// Retrieves the next item from an iterator.
/// Returns `None` when the iterator is exhausted.
pub fn iter_next(&self, py: Python) -> PyResult<Option<PyObject>> {
match unsafe { PyObject::from_owned_ptr_opt(py, ffi::PyIter_Next(self.as_ptr())) } {
Some(obj) => Ok(Some(obj)),
/// If an exception occurs, returns `Some(Err(..))`.
/// Further next() calls after an exception occurs are likely
/// to repeatedly result in the same exception.
fn next(&mut self) -> Option<PyResult<PyObject>> {
let py = self.py;
match unsafe { PyObject::from_owned_ptr_opt(py, ffi::PyIter_Next(self.iter.as_ptr())) } {
Some(obj) => Some(Ok(obj)),
None => {
if PyErr::occurred(py) {
Err(PyErr::fetch(py))
Some(Err(PyErr::fetch(py)))
} else {
Ok(None)
None
}
}
}
}
}
#[cfg(test)]
mod tests {
use python::{Python, PythonObject};
use conversion::ToPyObject;
use objectprotocol::ObjectProtocol;
#[test]
fn vec_iter() {
let gil_guard = Python::acquire_gil();
let py = gil_guard.python();
let obj = vec![10, 20].to_py_object(py).into_object();
let mut it = obj.iter(py).unwrap();
assert_eq!(10, it.next().unwrap().unwrap().extract(py).unwrap());
assert_eq!(20, it.next().unwrap().unwrap().extract(py).unwrap());
assert!(it.next().is_none());
}
}

View File

@ -16,7 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use python::{Python, PythonObject, ToPythonPointer};
use python::{Python, PythonObject, ToPythonPointer, PyClone};
use err::{self, PyErr, PyResult};
use super::object::PyObject;
use ffi::{self, Py_ssize_t};
@ -75,42 +75,27 @@ impl PyList {
let r = unsafe { ffi::PyList_Insert(self.0.as_ptr(), index as Py_ssize_t, item.as_ptr()) };
assert!(r == 0);
}
}
/*
impl <'p> IntoIterator for PyList {
type Item = PyObject;
type IntoIter = PyListIterator<'p>;
#[inline]
fn into_iter(self) -> PyListIterator<'p> {
PyListIterator { list: self, index: 0 }
pub fn iter<'a, 'p>(&'a self, py: Python<'p>) -> PyListIterator<'a, 'p> {
PyListIterator { py: py, list: self, index: 0 }
}
}
impl <'a, 'p> IntoIterator for &'a PyList {
type Item = PyObject;
type IntoIter = PyListIterator<'p>;
#[inline]
fn into_iter(self) -> PyListIterator<'p> {
PyListIterator { list: self.clone(), index: 0 }
}
}
/// Used by `impl IntoIterator for &PyList`.
TODO pub struct PyListIterator<'p> {
list: PyList,
/// Used by `PyList::iter()`.
pub struct PyListIterator<'a, 'p> {
py: Python<'p>,
list: &'a PyList,
index: usize
}
impl <'p> Iterator for PyListIterator<'p> {
impl <'a, 'p> Iterator for PyListIterator<'a, 'p> {
type Item = PyObject;
#[inline]
fn next(&mut self) -> Option<PyObject> {
if self.index < self.list.len() {
let item = self.list.get_item(self.index);
if self.index < self.list.len(self.py) {
let item = self.list.get_item(self.py, self.index);
self.index += 1;
Some(item)
} else {
@ -121,7 +106,6 @@ impl <'p> Iterator for PyListIterator<'p> {
// Note: we cannot implement size_hint because the length of the list
// might change during the iteration.
}
*/
impl <T> ToPyObject for [T] where T: ToPyObject {
type ObjectType = PyList;
@ -165,7 +149,6 @@ impl <'prepared, T> ExtractPyObject<'prepared> for Vec<T>
#[cfg(test)]
mod test {
use std;
use python::{Python, PythonObject};
use conversion::ToPyObject;
use objects::PyList;
@ -218,36 +201,20 @@ mod test {
assert_eq!(2, list.get_item(py, 1).extract::<i32>(py).unwrap());
}
/*
#[test]
fn test_iter() { TODO
fn test_iter() {
let gil = Python::acquire_gil();
let py = gil.python();
let v = vec![2, 3, 5, 7];
let list = v.to_py_object(py);
let mut idx = 0;
for el in list {
for el in list.iter(py) {
assert_eq!(v[idx], el.extract::<i32>(py).unwrap());
idx += 1;
}
assert_eq!(idx, v.len());
}
#[test]
fn test_into_iter() {
let gil = Python::acquire_gil();
let py = gil.python();
let v = vec![2, 3, 5, 7];
let list = v.to_py_object(py);
let mut idx = 0;
for el in list.into_iter() {
assert_eq!(v[idx], el.extract::<i32>().unwrap());
idx += 1;
}
assert_eq!(idx, v.len());
}
*/
#[test]
fn test_extract() {
let gil = Python::acquire_gil();

View File

@ -27,7 +27,6 @@ pub use self::string::PyBytes as PyString;
#[cfg(feature="python3-sys")]
pub use self::string::PyUnicode as PyString;
#[cfg(feature="python27-sys")]
pub use self::iterator::PyIterator;
pub use self::boolobject::PyBool;
pub use self::tuple::{PyTuple, NoArgs};
@ -160,7 +159,6 @@ mod typeobject;
mod module;
mod string;
mod dict;
#[cfg(feature="python27-sys")]
mod iterator;
mod boolobject;
mod tuple;

View File

@ -20,7 +20,7 @@ use std::mem;
use ffi;
use python::{Python, PythonObject, ToPythonPointer, PyClone};
use conversion::ToPyObject;
use objects::{PyObject, PyList, PyTuple};
use objects::{PyObject, PyList, PyTuple, PyIterator};
use ffi::Py_ssize_t;
use err;
use err::{PyErr, PyResult, result_from_owned_ptr, result_cast_from_owned_ptr};
@ -199,50 +199,11 @@ impl PySequence {
result_cast_from_owned_ptr(py, ffi::PySequence_Tuple(self.as_ptr()))
}
}
#[inline]
pub fn iter<'p>(&self, py: Python<'p>) -> PySequenceIterator<'p> {
PySequenceIterator {
sequence: self.clone_ref(py),
index: 0,
py: py
}
}
#[inline]
pub fn into_iter<'p>(self, py: Python<'p>) -> PySequenceIterator<'p> {
PySequenceIterator {
sequence: self,
index: 0,
py: py
}
}
}
pub struct PySequenceIterator<'p> {
sequence : PySequence,
index : isize,
py : Python<'p>
}
impl <'p> Iterator for PySequenceIterator<'p> {
// TODO: reconsider error reporting; maybe this should be Item = PyResult<PyObject>?
type Item = PyObject;
fn next(&mut self) -> Option<PyObject> {
// can't report any errors in underlying size check so we panic.
let len = self.sequence.len(self.py).unwrap();
if self.index < len {
match self.sequence.get_item(self.py, self.index) {
Ok(item) => {
self.index += 1;
Some(item)
},
Err(_) => None
}
} else {
None
}
pub fn iter<'p>(&self, py: Python<'p>) -> PyResult<PyIterator<'p>> {
use objectprotocol::ObjectProtocol;
self.as_object().iter(py)
}
}
@ -251,7 +212,7 @@ mod test {
use std;
use python::{Python, PythonObject};
use conversion::ToPyObject;
use objects::{PySequence, PyList, PyTuple};
use objects::{PySequence, PyList, PyTuple, PyIterator};
#[test]
fn test_numbers_are_not_sequences() {
@ -373,36 +334,20 @@ mod test {
assert_eq!(0, seq.count(py, 42i32).unwrap());
}
/*
#[test]
fn test_seq_iter() { TODO
fn test_seq_iter() {
let gil = Python::acquire_gil();
let py = gil.python();
let v : Vec<i32> = vec![1, 1, 2, 3, 5, 8];
let seq = v.to_py_object(py).into_object().cast_into::<PySequence>(py).unwrap();
let mut idx = 0;
for el in seq {
assert_eq!(v[idx], el.extract::<i32>(py).unwrap());
for el in seq.iter(py).unwrap() {
assert_eq!(v[idx], el.unwrap().extract::<i32>(py).unwrap());
idx += 1;
}
assert_eq!(idx, v.len());
}
#[test]
fn test_seq_into_iter() {
let gil = Python::acquire_gil();
let py = gil.python();
let v : Vec<i32> = vec![1, 1, 2, 3, 5, 8];
let seq = v.to_py_object(py).into_object().cast_into::<PySequence>().unwrap();
let mut idx = 0;
for el in seq.into_iter() {
assert_eq!(v[idx], el.extract::<i32>().unwrap());
idx += 1;
}
assert_eq!(idx, v.len());
}
*/
#[test]
fn test_seq_strings() {
let gil = Python::acquire_gil();
@ -426,8 +371,8 @@ mod test {
let concat_seq = seq.concat(py, &seq).unwrap().cast_into::<PySequence>(py).unwrap();
assert_eq!(6, concat_seq.len(py).unwrap());
let concat_v : Vec<i32> = vec![1, 2, 3, 1, 2, 3];
for (el, cc) in seq.into_iter(py).zip(concat_v) {
assert_eq!(cc, el.extract::<i32>(py).unwrap());
for (el, cc) in seq.iter(py).unwrap().zip(concat_v) {
assert_eq!(cc, el.unwrap().extract::<i32>(py).unwrap());
}
}
@ -454,8 +399,8 @@ mod test {
let repeat_seq = seq.repeat(py, 3).unwrap().cast_into::<PySequence>(py).unwrap();
assert_eq!(6, repeat_seq.len(py).unwrap());
let repeated = vec!["foo", "bar", "foo", "bar", "foo", "bar"];
for (el, rpt) in seq.into_iter(py).zip(repeated.iter()) {
assert_eq!(*rpt, el.extract::<String>(py).unwrap());
for (el, rpt) in seq.iter(py).unwrap().zip(repeated.iter()) {
assert_eq!(*rpt, el.unwrap().extract::<String>(py).unwrap());
}
}

View File

@ -84,45 +84,52 @@ impl PyTuple {
self.len()))
}
}
}
// TODO impl Index for PyTuple
/*
impl IntoIterator for PyTuple { TODO
type Item = PyObject;
type IntoIter = PyTupleIterator;
#[inline]
fn into_iter(self) -> PyTupleIterator {
PyTupleIterator { index: 0, end: self.len(), tuple: self }
pub fn iter<'a, 'p>(&'a self, py: Python<'p>) -> PyTupleIterator<'a, 'p> {
PyTupleIterator {
py: py,
tuple: self,
index: 0,
end: self.len()
}
}
}
impl ::std::ops::Index<usize> for PyTuple {
type Output = PyObject;
#[inline]
fn index(&self, index: usize) -> &PyObject {
&self.as_slice()[index]
}
}
impl <'a> IntoIterator for &'a PyTuple {
type Item = PyObject<'p>;
type IntoIter = PyTupleIterator<'p>;
type Item = &'a PyObject;
type IntoIter = slice::Iter<'a, PyObject>;
#[inline]
fn into_iter(self) -> PyTupleIterator<'p> {
PyTupleIterator { index: 0, end: self.len(), tuple: self.clone() }
fn into_iter(self) -> Self::IntoIter {
self.as_slice().iter()
}
}
/// Used by `impl IntoIterator for &PyTuple`.
pub struct PyTupleIterator<'p> {
tuple: PyTuple<'p>,
/// Used by `PyTuple::iter()`.
pub struct PyTupleIterator<'a, 'p> {
py: Python<'p>,
tuple: &'a PyTuple,
index: usize,
end: usize
}
impl <'p> Iterator for PyTupleIterator<'p> {
type Item = PyObject<'p>;
impl <'a, 'p> Iterator for PyTupleIterator<'a, 'p> {
type Item = PyObject;
#[inline]
fn next(&mut self) -> Option<PyObject<'p>> {
fn next(&mut self) -> Option<PyObject> {
if self.index < self.end {
let item = self.tuple.get_item(self.index);
let item = self.tuple.get_item(self.py, self.index);
self.index += 1;
Some(item)
} else {
@ -136,13 +143,12 @@ impl <'p> Iterator for PyTupleIterator<'p> {
}
}
impl <'p> ExactSizeIterator for PyTupleIterator<'p> {
impl <'a, 'p> ExactSizeIterator for PyTupleIterator<'a, 'p> {
#[inline]
fn len(&self) -> usize {
self.end - self.index
}
}
*/
fn wrong_tuple_length(py: Python, t: &PyTuple, expected_length: usize) -> PyErr {
let msg = format!("Expected tuple of length {}, but got tuple of length {}.", expected_length, t.len());
@ -232,3 +238,19 @@ extract!(obj to NoArgs; py => {
}
});
#[cfg(test)]
mod test {
use python::{Python, PythonObject};
use conversion::ToPyObject;
#[test]
fn test_len() {
let gil = Python::acquire_gil();
let py = gil.python();
let tuple = (1, 2, 3).to_py_object(py);
assert_eq!(3, tuple.len());
}
}