Skip to content

Commit e32641a

Browse files
Aviram Hassandavidhewitt
authored andcommitted
- PyList, PyTuple and PySequence's get_item now accepts only usize indices instead of isize.
- `PyList` and `PyTuple`'s `get_item` now always uses the safe API. See `get_item_unchecked` for retrieving index without checks.
1 parent df41903 commit e32641a

File tree

9 files changed

+216
-86
lines changed

9 files changed

+216
-86
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88

99
## [Unreleased]
1010

11+
### Added
12+
- Add `PyList::get_item_unchecked()` and `PyTuple::get_item_unchecked()` to get items without bounds checks. [#1733](https://github.com/PyO3/pyo3/pull/1733)
13+
1114
### Changed
1215

1316
- Change `PyErr::fetch()` to return `Option<PyErr>`. [#1717](https://github.com/PyO3/pyo3/pull/1717)
17+
- `PyList`, `PyTuple` and `PySequence`'s `get_item` now accepts only `usize` indices instead of `isize`. [#1733](https://github.com/PyO3/pyo3/pull/1733)
18+
- `PyList` and `PyTuple`'s `get_item` now return `PyResult<&PyAny> instead of panicking. [#1733](https://github.com/PyO3/pyo3/pull/1733)
1419

1520
### Fixed
1621

benches/bench_list.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,22 @@ fn list_get_item(b: &mut Bencher) {
3232
let mut sum = 0;
3333
b.iter(|| {
3434
for i in 0..LEN {
35-
sum += list.get_item(i as isize).extract::<usize>().unwrap();
35+
sum += list.get_item(i).unwrap().extract::<usize>().unwrap();
36+
}
37+
});
38+
}
39+
40+
fn list_get_item_unchecked(b: &mut Bencher) {
41+
let gil = Python::acquire_gil();
42+
let py = gil.python();
43+
const LEN: usize = 50_000;
44+
let list = PyList::new(py, 0..LEN);
45+
let mut sum = 0;
46+
b.iter(|| {
47+
for i in 0..LEN {
48+
unsafe {
49+
sum += list.get_item_unchecked(i).extract::<usize>().unwrap();
50+
}
3651
}
3752
});
3853
}
@@ -41,6 +56,7 @@ fn criterion_benchmark(c: &mut Criterion) {
4156
c.bench_function("iter_list", iter_list);
4257
c.bench_function("list_new", list_new);
4358
c.bench_function("list_get_item", list_get_item);
59+
c.bench_function("list_get_item_unchecked", list_get_item_unchecked);
4460
}
4561

4662
criterion_group!(benches, criterion_benchmark);

benches/bench_tuple.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,22 @@ fn tuple_get_item(b: &mut Bencher) {
3232
let mut sum = 0;
3333
b.iter(|| {
3434
for i in 0..LEN {
35-
sum += tuple.get_item(i).extract::<usize>().unwrap();
35+
sum += tuple.get_item(i).unwrap().extract::<usize>().unwrap();
36+
}
37+
});
38+
}
39+
40+
fn tuple_get_item_unchecked(b: &mut Bencher) {
41+
let gil = Python::acquire_gil();
42+
let py = gil.python();
43+
const LEN: usize = 50_000;
44+
let tuple = PyTuple::new(py, 0..LEN);
45+
let mut sum = 0;
46+
b.iter(|| {
47+
for i in 0..LEN {
48+
unsafe {
49+
sum += tuple.get_item_unchecked(i).extract::<usize>().unwrap();
50+
}
3651
}
3752
});
3853
}
@@ -41,6 +56,7 @@ fn criterion_benchmark(c: &mut Criterion) {
4156
c.bench_function("iter_tuple", iter_tuple);
4257
c.bench_function("tuple_new", tuple_new);
4358
c.bench_function("tuple_get_item", tuple_get_item);
59+
c.bench_function("tuple_get_item_unchecked", tuple_get_item_unchecked);
4460
}
4561

4662
criterion_group!(benches, criterion_benchmark);

pyo3-macros-backend/src/from_pyobject.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ impl<'a> Container<'a> {
243243
for i in 0..len {
244244
let error_msg = format!("failed to extract field {}.{}", quote!(#self_ty), i);
245245
fields.push(quote!(
246-
s.get_item(#i).extract().map_err(|inner| {
246+
s.get_item(#i).and_then(PyAny::extract).map_err(|inner| {
247247
let py = pyo3::PyNativeType::py(obj);
248248
let new_err = pyo3::exceptions::PyTypeError::new_err(#error_msg);
249249
new_err.set_cause(py, Some(inner));

src/conversions/array.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ mod min_const_generics {
6060
if seq_len != N {
6161
return Err(invalid_sequence_length(N, seq_len));
6262
}
63-
array_try_from_fn(|idx| seq.get_item(idx as isize).and_then(PyAny::extract))
63+
array_try_from_fn(|idx| seq.get_item(idx).and_then(PyAny::extract))
6464
}
6565

6666
// TODO use std::array::try_from_fn, if that stabilises:

src/types/dict.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,8 +639,8 @@ mod tests {
639639
let mut value_sum = 0;
640640
for el in dict.items().iter() {
641641
let tuple = el.cast_as::<PyTuple>().unwrap();
642-
key_sum += tuple.get_item(0).extract::<i32>().unwrap();
643-
value_sum += tuple.get_item(1).extract::<i32>().unwrap();
642+
key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
643+
value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
644644
}
645645
assert_eq!(7 + 8 + 9, key_sum);
646646
assert_eq!(32 + 42 + 123, value_sum);

src/types/list.rs

Lines changed: 102 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,39 @@ impl PyList {
6767
self.len() == 0
6868
}
6969

70-
/// Gets the item at the specified index.
71-
///
72-
/// Panics if the index is out of range.
73-
pub fn get_item(&self, index: isize) -> &PyAny {
74-
assert!(index >= 0 && index < self.len() as isize);
70+
/// Gets the list item at the specified index.
71+
/// # Example
72+
/// ```
73+
/// use pyo3::{prelude::*, types::PyList};
74+
/// Python::with_gil(|py| {
75+
/// let list = PyList::new(py, &[2, 3, 5, 7]);
76+
/// let obj = list.get_item(0);
77+
/// assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 2);
78+
/// });
79+
/// ```
80+
pub fn get_item(&self, index: usize) -> PyResult<&PyAny> {
7581
unsafe {
76-
#[cfg(not(Py_LIMITED_API))]
77-
let ptr = ffi::PyList_GET_ITEM(self.as_ptr(), index as Py_ssize_t);
78-
#[cfg(Py_LIMITED_API)]
79-
let ptr = ffi::PyList_GetItem(self.as_ptr(), index as Py_ssize_t);
80-
82+
let item = ffi::PyList_GetItem(self.as_ptr(), index as Py_ssize_t);
8183
// PyList_GetItem return borrowed ptr; must make owned for safety (see #890).
82-
ffi::Py_INCREF(ptr);
83-
self.py().from_owned_ptr(ptr)
84+
ffi::Py_XINCREF(item);
85+
self.py().from_owned_ptr_or_err(item)
8486
}
8587
}
8688

89+
/// Gets the list item at the specified index. Undefined behavior on bad index. Use with caution.
90+
///
91+
/// # Safety
92+
///
93+
/// Caller must verify that the index is within the bounds of the list.
94+
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
95+
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
96+
pub unsafe fn get_item_unchecked(&self, index: usize) -> &PyAny {
97+
let item = ffi::PyList_GET_ITEM(self.as_ptr(), index as Py_ssize_t);
98+
// PyList_GET_ITEM return borrowed ptr; must make owned for safety (see #890).
99+
ffi::Py_XINCREF(item);
100+
self.py().from_owned_ptr(item)
101+
}
102+
87103
/// Takes the slice `self[low:high]` and returns it as a new list.
88104
///
89105
/// Indices must be nonnegative, and out-of-range indices are clipped to
@@ -164,16 +180,19 @@ impl PyList {
164180
/// Used by `PyList::iter()`.
165181
pub struct PyListIterator<'a> {
166182
list: &'a PyList,
167-
index: isize,
183+
index: usize,
168184
}
169185

170186
impl<'a> Iterator for PyListIterator<'a> {
171187
type Item = &'a PyAny;
172188

173189
#[inline]
174190
fn next(&mut self) -> Option<&'a PyAny> {
175-
if self.index < self.list.len() as isize {
176-
let item = self.list.get_item(self.index);
191+
if self.index < self.list.len() {
192+
#[cfg(any(Py_LIMITED_API, PyPy))]
193+
let item = self.list.get_item(self.index).expect("tuple.get failed");
194+
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
195+
let item = unsafe { self.list.get_item_unchecked(self.index) };
177196
self.index += 1;
178197
Some(item)
179198
} else {
@@ -186,8 +205,8 @@ impl<'a> Iterator for PyListIterator<'a> {
186205
let len = self.list.len();
187206

188207
(
189-
len.saturating_sub(self.index as usize),
190-
Some(len.saturating_sub(self.index as usize)),
208+
len.saturating_sub(self.index),
209+
Some(len.saturating_sub(self.index)),
191210
)
192211
}
193212
}
@@ -238,10 +257,10 @@ mod tests {
238257
fn test_new() {
239258
Python::with_gil(|py| {
240259
let list = PyList::new(py, &[2, 3, 5, 7]);
241-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
242-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
243-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
244-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
260+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
261+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
262+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
263+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
245264
});
246265
}
247266

@@ -257,19 +276,10 @@ mod tests {
257276
fn test_get_item() {
258277
Python::with_gil(|py| {
259278
let list = PyList::new(py, &[2, 3, 5, 7]);
260-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
261-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
262-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
263-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
264-
});
265-
}
266-
267-
#[test]
268-
#[should_panic]
269-
fn test_get_item_invalid() {
270-
Python::with_gil(|py| {
271-
let list = PyList::new(py, &[2, 3, 5, 7]);
272-
list.get_item(-1);
279+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
280+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
281+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
282+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
273283
});
274284
}
275285

@@ -290,9 +300,9 @@ mod tests {
290300
let list = PyList::new(py, &[2, 3, 5, 7]);
291301
let val = 42i32.to_object(py);
292302
let val2 = 42i32.to_object(py);
293-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
303+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
294304
list.set_item(0, val).unwrap();
295-
assert_eq!(42, list.get_item(0).extract::<i32>().unwrap());
305+
assert_eq!(42, list.get_item(0).unwrap().extract::<i32>().unwrap());
296306
assert!(list.set_item(10, val2).is_err());
297307
});
298308
}
@@ -322,13 +332,13 @@ mod tests {
322332
let val = 42i32.to_object(py);
323333
let val2 = 43i32.to_object(py);
324334
assert_eq!(4, list.len());
325-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
335+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
326336
list.insert(0, val).unwrap();
327337
list.insert(1000, val2).unwrap();
328338
assert_eq!(6, list.len());
329-
assert_eq!(42, list.get_item(0).extract::<i32>().unwrap());
330-
assert_eq!(2, list.get_item(1).extract::<i32>().unwrap());
331-
assert_eq!(43, list.get_item(5).extract::<i32>().unwrap());
339+
assert_eq!(42, list.get_item(0).unwrap().extract::<i32>().unwrap());
340+
assert_eq!(2, list.get_item(1).unwrap().extract::<i32>().unwrap());
341+
assert_eq!(43, list.get_item(5).unwrap().extract::<i32>().unwrap());
332342
});
333343
}
334344

@@ -353,8 +363,8 @@ mod tests {
353363
Python::with_gil(|py| {
354364
let list = PyList::new(py, &[2]);
355365
list.append(3).unwrap();
356-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
357-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
366+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
367+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
358368
});
359369
}
360370

@@ -431,15 +441,15 @@ mod tests {
431441
Python::with_gil(|py| {
432442
let v = vec![7, 3, 2, 5];
433443
let list = PyList::new(py, &v);
434-
assert_eq!(7, list.get_item(0).extract::<i32>().unwrap());
435-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
436-
assert_eq!(2, list.get_item(2).extract::<i32>().unwrap());
437-
assert_eq!(5, list.get_item(3).extract::<i32>().unwrap());
444+
assert_eq!(7, list.get_item(0).unwrap().extract::<i32>().unwrap());
445+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
446+
assert_eq!(2, list.get_item(2).unwrap().extract::<i32>().unwrap());
447+
assert_eq!(5, list.get_item(3).unwrap().extract::<i32>().unwrap());
438448
list.sort().unwrap();
439-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
440-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
441-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
442-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
449+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
450+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
451+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
452+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
443453
});
444454
}
445455

@@ -448,15 +458,15 @@ mod tests {
448458
Python::with_gil(|py| {
449459
let v = vec![2, 3, 5, 7];
450460
let list = PyList::new(py, &v);
451-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
452-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
453-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
454-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
461+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
462+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
463+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
464+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
455465
list.reverse().unwrap();
456-
assert_eq!(7, list.get_item(0).extract::<i32>().unwrap());
457-
assert_eq!(5, list.get_item(1).extract::<i32>().unwrap());
458-
assert_eq!(3, list.get_item(2).extract::<i32>().unwrap());
459-
assert_eq!(2, list.get_item(3).extract::<i32>().unwrap());
466+
assert_eq!(7, list.get_item(0).unwrap().extract::<i32>().unwrap());
467+
assert_eq!(5, list.get_item(1).unwrap().extract::<i32>().unwrap());
468+
assert_eq!(3, list.get_item(2).unwrap().extract::<i32>().unwrap());
469+
assert_eq!(2, list.get_item(3).unwrap().extract::<i32>().unwrap());
460470
});
461471
}
462472

@@ -465,8 +475,40 @@ mod tests {
465475
Python::with_gil(|py| {
466476
let array: PyObject = [1, 2].into_py(py);
467477
let list = <PyList as PyTryFrom>::try_from(array.as_ref(py)).unwrap();
468-
assert_eq!(1, list.get_item(0).extract::<i32>().unwrap());
469-
assert_eq!(2, list.get_item(1).extract::<i32>().unwrap());
478+
assert_eq!(1, list.get_item(0).unwrap().extract::<i32>().unwrap());
479+
assert_eq!(2, list.get_item(1).unwrap().extract::<i32>().unwrap());
480+
});
481+
}
482+
483+
#[test]
484+
fn test_list_get_item_invalid_index() {
485+
Python::with_gil(|py| {
486+
let list = PyList::new(py, &[2, 3, 5, 7]);
487+
let obj = list.get_item(5);
488+
assert!(obj.is_err());
489+
assert_eq!(
490+
obj.unwrap_err().to_string(),
491+
"IndexError: list index out of range"
492+
);
493+
});
494+
}
495+
496+
#[test]
497+
fn test_list_get_item_sanity() {
498+
Python::with_gil(|py| {
499+
let list = PyList::new(py, &[2, 3, 5, 7]);
500+
let obj = list.get_item(0);
501+
assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 2);
502+
});
503+
}
504+
505+
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
506+
#[test]
507+
fn test_list_get_item_unchecked_sanity() {
508+
Python::with_gil(|py| {
509+
let list = PyList::new(py, &[2, 3, 5, 7]);
510+
let obj = unsafe { list.get_item_unchecked(0) };
511+
assert_eq!(obj.extract::<i32>().unwrap(), 2);
470512
});
471513
}
472514
}

src/types/sequence.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ impl PySequence {
100100

101101
/// Returns the `index`th element of the Sequence.
102102
///
103-
/// This is equivalent to the Python expression `self[index]`.
103+
/// This is equivalent to the Python expression `self[index]` without support of negative indices.
104104
#[inline]
105-
pub fn get_item(&self, index: isize) -> PyResult<&PyAny> {
105+
pub fn get_item(&self, index: usize) -> PyResult<&PyAny> {
106106
unsafe {
107107
self.py()
108108
.from_owned_ptr_or_err(ffi::PySequence_GetItem(self.as_ptr(), index as Py_ssize_t))
@@ -404,11 +404,6 @@ mod tests {
404404
assert_eq!(3, seq.get_item(3).unwrap().extract::<i32>().unwrap());
405405
assert_eq!(5, seq.get_item(4).unwrap().extract::<i32>().unwrap());
406406
assert_eq!(8, seq.get_item(5).unwrap().extract::<i32>().unwrap());
407-
assert_eq!(8, seq.get_item(-1).unwrap().extract::<i32>().unwrap());
408-
assert_eq!(5, seq.get_item(-2).unwrap().extract::<i32>().unwrap());
409-
assert_eq!(3, seq.get_item(-3).unwrap().extract::<i32>().unwrap());
410-
assert_eq!(2, seq.get_item(-4).unwrap().extract::<i32>().unwrap());
411-
assert_eq!(1, seq.get_item(-5).unwrap().extract::<i32>().unwrap());
412407
assert!(seq.get_item(10).is_err());
413408
});
414409
}

0 commit comments

Comments
 (0)