Skip to content

Commit c6255e6

Browse files
Aviram Hassandavidhewitt
Aviram Hassan
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 7a92e26 commit c6255e6

File tree

9 files changed

+214
-86
lines changed

9 files changed

+214
-86
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1010

1111
### Added
1212

13+
- Add `PyList::get_item_unchecked()` and `PyTuple::get_item_unchecked()` to get items without bounds checks. [#1733](https://github.com/PyO3/pyo3/pull/1733)
1314
- Add `PyAny::py()` as a convenience for `PyNativeType::py()`. [#1751](https://github.com/PyO3/pyo3/pull/1751)
1415

1516
### Changed
1617

1718
- Change `PyErr::fetch()` to return `Option<PyErr>`. [#1717](https://github.com/PyO3/pyo3/pull/1717)
19+
- `PyList`, `PyTuple` and `PySequence`'s `get_item` now accepts only `usize` indices instead of `isize`. [#1733](https://github.com/PyO3/pyo3/pull/1733)
20+
- `PyList` and `PyTuple`'s `get_item` now return `PyResult<&PyAny> instead of panicking. [#1733](https://github.com/PyO3/pyo3/pull/1733)
1821
- Deprecate `PyTuple::split_from`. [#1804](https://github.com/PyO3/pyo3/pull/1804)
1922

2023
### Fixed

benches/bench_list.rs

+17-1
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

+17-1
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

+1-1
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

+1-1
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

+2-2
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

+102-60
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
/// Takes the slice `self[low:high]` and returns it as a new list.
87103
///
88104
/// Indices must be nonnegative, and out-of-range indices are clipped to
@@ -163,16 +179,19 @@ impl PyList {
163179
/// Used by `PyList::iter()`.
164180
pub struct PyListIterator<'a> {
165181
list: &'a PyList,
166-
index: isize,
182+
index: usize,
167183
}
168184

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

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

187206
(
188-
len.saturating_sub(self.index as usize),
189-
Some(len.saturating_sub(self.index as usize)),
207+
len.saturating_sub(self.index),
208+
Some(len.saturating_sub(self.index)),
190209
)
191210
}
192211
}
@@ -237,10 +256,10 @@ mod tests {
237256
fn test_new() {
238257
Python::with_gil(|py| {
239258
let list = PyList::new(py, &[2, 3, 5, 7]);
240-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
241-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
242-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
243-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
259+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
260+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
261+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
262+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
244263
});
245264
}
246265

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

@@ -289,9 +299,9 @@ mod tests {
289299
let list = PyList::new(py, &[2, 3, 5, 7]);
290300
let val = 42i32.to_object(py);
291301
let val2 = 42i32.to_object(py);
292-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
302+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
293303
list.set_item(0, val).unwrap();
294-
assert_eq!(42, list.get_item(0).extract::<i32>().unwrap());
304+
assert_eq!(42, list.get_item(0).unwrap().extract::<i32>().unwrap());
295305
assert!(list.set_item(10, val2).is_err());
296306
});
297307
}
@@ -321,13 +331,13 @@ mod tests {
321331
let val = 42i32.to_object(py);
322332
let val2 = 43i32.to_object(py);
323333
assert_eq!(4, list.len());
324-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
334+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
325335
list.insert(0, val).unwrap();
326336
list.insert(1000, val2).unwrap();
327337
assert_eq!(6, list.len());
328-
assert_eq!(42, list.get_item(0).extract::<i32>().unwrap());
329-
assert_eq!(2, list.get_item(1).extract::<i32>().unwrap());
330-
assert_eq!(43, list.get_item(5).extract::<i32>().unwrap());
338+
assert_eq!(42, list.get_item(0).unwrap().extract::<i32>().unwrap());
339+
assert_eq!(2, list.get_item(1).unwrap().extract::<i32>().unwrap());
340+
assert_eq!(43, list.get_item(5).unwrap().extract::<i32>().unwrap());
331341
});
332342
}
333343

@@ -352,8 +362,8 @@ mod tests {
352362
Python::with_gil(|py| {
353363
let list = PyList::new(py, &[2]);
354364
list.append(3).unwrap();
355-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
356-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
365+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
366+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
357367
});
358368
}
359369

@@ -430,15 +440,15 @@ mod tests {
430440
Python::with_gil(|py| {
431441
let v = vec![7, 3, 2, 5];
432442
let list = PyList::new(py, &v);
433-
assert_eq!(7, list.get_item(0).extract::<i32>().unwrap());
434-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
435-
assert_eq!(2, list.get_item(2).extract::<i32>().unwrap());
436-
assert_eq!(5, list.get_item(3).extract::<i32>().unwrap());
443+
assert_eq!(7, list.get_item(0).unwrap().extract::<i32>().unwrap());
444+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
445+
assert_eq!(2, list.get_item(2).unwrap().extract::<i32>().unwrap());
446+
assert_eq!(5, list.get_item(3).unwrap().extract::<i32>().unwrap());
437447
list.sort().unwrap();
438-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
439-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
440-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
441-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
448+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
449+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
450+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
451+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
442452
});
443453
}
444454

@@ -447,15 +457,15 @@ mod tests {
447457
Python::with_gil(|py| {
448458
let v = vec![2, 3, 5, 7];
449459
let list = PyList::new(py, &v);
450-
assert_eq!(2, list.get_item(0).extract::<i32>().unwrap());
451-
assert_eq!(3, list.get_item(1).extract::<i32>().unwrap());
452-
assert_eq!(5, list.get_item(2).extract::<i32>().unwrap());
453-
assert_eq!(7, list.get_item(3).extract::<i32>().unwrap());
460+
assert_eq!(2, list.get_item(0).unwrap().extract::<i32>().unwrap());
461+
assert_eq!(3, list.get_item(1).unwrap().extract::<i32>().unwrap());
462+
assert_eq!(5, list.get_item(2).unwrap().extract::<i32>().unwrap());
463+
assert_eq!(7, list.get_item(3).unwrap().extract::<i32>().unwrap());
454464
list.reverse().unwrap();
455-
assert_eq!(7, list.get_item(0).extract::<i32>().unwrap());
456-
assert_eq!(5, list.get_item(1).extract::<i32>().unwrap());
457-
assert_eq!(3, list.get_item(2).extract::<i32>().unwrap());
458-
assert_eq!(2, list.get_item(3).extract::<i32>().unwrap());
465+
assert_eq!(7, list.get_item(0).unwrap().extract::<i32>().unwrap());
466+
assert_eq!(5, list.get_item(1).unwrap().extract::<i32>().unwrap());
467+
assert_eq!(3, list.get_item(2).unwrap().extract::<i32>().unwrap());
468+
assert_eq!(2, list.get_item(3).unwrap().extract::<i32>().unwrap());
459469
});
460470
}
461471

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

src/types/sequence.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ impl PySequence {
9999

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

0 commit comments

Comments
 (0)