Skip to content

Commit e716276

Browse files
author
Aviram Hassan
committed
- 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 388c255 commit e716276

File tree

9 files changed

+215
-85
lines changed

9 files changed

+215
-85
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: 101 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -66,23 +66,39 @@ impl PyList {
6666
self.len() == 0
6767
}
6868

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

88+
/// Gets the list item at the specified index. Undefined behavior on bad index. Use with caution.
89+
///
90+
/// # Safety
91+
///
92+
/// Caller must verify that the index is within the bounds of the list.
93+
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
94+
#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))]
95+
pub unsafe fn get_item_unchecked(&self, index: usize) -> &PyAny {
96+
let item = ffi::PyList_GET_ITEM(self.as_ptr(), index as Py_ssize_t);
97+
// PyList_GET_ITEM return borrowed ptr; must make owned for safety (see #890).
98+
ffi::Py_XINCREF(item);
99+
self.py().from_owned_ptr(item)
100+
}
101+
86102
/// Sets the item at the specified index.
87103
///
88104
/// Panics if the index is out of range.
@@ -142,16 +158,19 @@ impl PyList {
142158
/// Used by `PyList::iter()`.
143159
pub struct PyListIterator<'a> {
144160
list: &'a PyList,
145-
index: isize,
161+
index: usize,
146162
}
147163

148164
impl<'a> Iterator for PyListIterator<'a> {
149165
type Item = &'a PyAny;
150166

151167
#[inline]
152168
fn next(&mut self) -> Option<&'a PyAny> {
153-
if self.index < self.list.len() as isize {
154-
let item = self.list.get_item(self.index);
169+
if self.index < self.list.len() {
170+
#[cfg(any(Py_LIMITED_API, PyPy))]
171+
let item = self.list.get_item(self.index).expect("tuple.get failed");
172+
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
173+
let item = unsafe { self.list.get_item_unchecked(self.index) };
155174
self.index += 1;
156175
Some(item)
157176
} else {
@@ -164,8 +183,8 @@ impl<'a> Iterator for PyListIterator<'a> {
164183
let len = self.list.len();
165184

166185
(
167-
len.saturating_sub(self.index as usize),
168-
Some(len.saturating_sub(self.index as usize)),
186+
len.saturating_sub(self.index),
187+
Some(len.saturating_sub(self.index)),
169188
)
170189
}
171190
}
@@ -216,10 +235,10 @@ mod tests {
216235
fn test_new() {
217236
Python::with_gil(|py| {
218237
let list = PyList::new(py, &[2, 3, 5, 7]);
219-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
220-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
221-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
222-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
238+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
239+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
240+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
241+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
223242
});
224243
}
225244

@@ -235,19 +254,10 @@ mod tests {
235254
fn test_get_item() {
236255
Python::with_gil(|py| {
237256
let list = PyList::new(py, &[2, 3, 5, 7]);
238-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
239-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
240-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
241-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
242-
});
243-
}
244-
245-
#[test]
246-
#[should_panic]
247-
fn test_get_item_invalid() {
248-
Python::with_gil(|py| {
249-
let list = PyList::new(py, &[2, 3, 5, 7]);
250-
list.get_item(-1);
257+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
258+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
259+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
260+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
251261
});
252262
}
253263

@@ -256,9 +266,9 @@ mod tests {
256266
Python::with_gil(|py| {
257267
let list = PyList::new(py, &[2, 3, 5, 7]);
258268
let val = 42i32.to_object(py);
259-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
269+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
260270
list.set_item(0, val).unwrap();
261-
assert_eq!(42, list.get_item(0).extract::<i32>().unwrap());
271+
assert_eq!(42, list.get_item(0).unwrap().extract::<i32>().unwrap());
262272
});
263273
}
264274

@@ -286,11 +296,11 @@ mod tests {
286296
let list = PyList::new(py, &[2, 3, 5, 7]);
287297
let val = 42i32.to_object(py);
288298
assert_eq!(4, list.len());
289-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
299+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
290300
list.insert(0, val).unwrap();
291301
assert_eq!(5, list.len());
292-
assert_eq!(42, list.get_item(0).extract::<i32>().unwrap());
293-
assert_eq!(2, list.get_item(1).extract::<i32>().unwrap());
302+
assert_eq!(42, list.get_item(0).unwrap().extract::<i32>().unwrap());
303+
assert_eq!(2, list.get_item(1).unwrap().extract::<i32>().unwrap());
294304
});
295305
}
296306

@@ -315,8 +325,8 @@ mod tests {
315325
Python::with_gil(|py| {
316326
let list = PyList::new(py, &[2]);
317327
list.append(3).unwrap();
318-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
319-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
328+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
329+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
320330
});
321331
}
322332

@@ -393,15 +403,15 @@ mod tests {
393403
Python::with_gil(|py| {
394404
let v = vec![7, 3, 2, 5];
395405
let list = PyList::new(py, &v);
396-
assert_eq!(7, list.get_item(0).extract::<i32>().unwrap());
397-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
398-
assert_eq!(2, list.get_item(2).extract::<i32>().unwrap());
399-
assert_eq!(5, list.get_item(3).extract::<i32>().unwrap());
406+
assert_eq!(7, list.get_item(0).unwrap().extract::<i32>().unwrap());
407+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
408+
assert_eq!(2, list.get_item(2).unwrap().extract::<i32>().unwrap());
409+
assert_eq!(5, list.get_item(3).unwrap().extract::<i32>().unwrap());
400410
list.sort().unwrap();
401-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
402-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
403-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
404-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
411+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
412+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
413+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
414+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
405415
});
406416
}
407417

@@ -410,15 +420,15 @@ mod tests {
410420
Python::with_gil(|py| {
411421
let v = vec![2, 3, 5, 7];
412422
let list = PyList::new(py, &v);
413-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
414-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
415-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
416-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
423+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
424+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
425+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
426+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
417427
list.reverse().unwrap();
418-
assert_eq!(7, list.get_item(0).extract::<i32>().unwrap());
419-
assert_eq!(5, list.get_item(1).extract::<i32>().unwrap());
420-
assert_eq!(3, list.get_item(2).extract::<i32>().unwrap());
421-
assert_eq!(2, list.get_item(3).extract::<i32>().unwrap());
428+
assert_eq!(7, list.get_item(0).unwrap().extract::<i32>().unwrap());
429+
assert_eq!(5, list.get_item(1).unwrap().extract::<i32>().unwrap());
430+
assert_eq!(3, list.get_item(2).unwrap().extract::<i32>().unwrap());
431+
assert_eq!(2, list.get_item(3).unwrap().extract::<i32>().unwrap());
422432
});
423433
}
424434

@@ -427,8 +437,40 @@ mod tests {
427437
Python::with_gil(|py| {
428438
let array: PyObject = [1, 2].into_py(py);
429439
let list = <PyList as PyTryFrom>::try_from(array.as_ref(py)).unwrap();
430-
assert_eq!(1, list.get_item(0).extract::<i32>().unwrap());
431-
assert_eq!(2, list.get_item(1).extract::<i32>().unwrap());
440+
assert_eq!(1, list.get_item(0).unwrap().extract::<i32>().unwrap());
441+
assert_eq!(2, list.get_item(1).unwrap().extract::<i32>().unwrap());
442+
});
443+
}
444+
445+
#[test]
446+
fn test_list_get_item_invalid_index() {
447+
Python::with_gil(|py| {
448+
let list = PyList::new(py, &[2, 3, 5, 7]);
449+
let obj = list.get_item(5);
450+
assert!(obj.is_err());
451+
assert_eq!(
452+
obj.unwrap_err().to_string(),
453+
"IndexError: list index out of range"
454+
);
455+
});
456+
}
457+
458+
#[test]
459+
fn test_list_get_item_sanity() {
460+
Python::with_gil(|py| {
461+
let list = PyList::new(py, &[2, 3, 5, 7]);
462+
let obj = list.get_item(0);
463+
assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 2);
464+
});
465+
}
466+
467+
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
468+
#[test]
469+
fn test_list_get_item_unchecked_sanity() {
470+
Python::with_gil(|py| {
471+
let list = PyList::new(py, &[2, 3, 5, 7]);
472+
let obj = unsafe { list.get_item_unchecked(0) };
473+
assert_eq!(obj.extract::<i32>().unwrap(), 2);
432474
});
433475
}
434476
}

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)