Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Lib/test/list_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,27 @@ def __eq__(self, other):
# This used to seg fault before patch #1005778
self.assertRaises(ValueError, a.index, None)

def test_index_key(self):
data = [(i, chr(i)) for i in range(100)]
idx = data.index("a", key=lambda i:i[1])
self.assertEqual(data[idx][1], "a")

with self.assertRaises(ValueError):
data.index("foo", key=lambda i:i[1])

# verify that errors propagate
with self.assertRaises(ZeroDivisionError):
data.index("a", key=lambda i: 1/0)

# verify that key method can modify the list without crashing
def evil_key(i):
del data[:]
return i[1]

with self.assertRaises(ValueError):

@sobolevn sobolevn Jan 7, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, assert the error message here. ValueError is too broad.

idx = data.index("a", key=evil_key)
self.assertEqual(data, [])

def test_reverse(self):
u = self.type2test([-2, -1, 0, 1, 2])
u2 = u[:]
Expand Down
4 changes: 0 additions & 4 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,6 @@ def test_varargs3_kw(self):
msg = r"bool\(\) takes no keyword arguments"
self.assertRaisesRegex(TypeError, msg, bool, x=2)

def test_varargs4_kw(self):
msg = r"^(list[.])?index\(\) takes no keyword arguments$"
self.assertRaisesRegex(TypeError, msg, [].index, x=2)

def test_varargs5_kw(self):
msg = r"^hasattr\(\) takes no keyword arguments$"
self.assertRaisesRegex(TypeError, msg, hasattr, x=2)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`list.index` now supports the *key* keyword-only argument. When provided, each list item is first transformed by it before comparint it with *value*.
60 changes: 49 additions & 11 deletions Objects/clinic/listobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 26 additions & 8 deletions Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2659,38 +2659,56 @@ list.index
start: slice_index(accept={int}) = 0
stop: slice_index(accept={int}, c_default="PY_SSIZE_T_MAX") = sys.maxsize
/
*
key: object=NULL

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AC has problems with NULL default. To check this please run inspect.signature(list.index) before and after.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you saying that object=NULL is nowhere used in the code despite being a documented convenience way of signifying optional PyObject values? So, either fix AC, or the docs?
I was an active contributor before AC was introduced and I'm only learning it now.
I guess you want me to turn this into object=None and make comparisons with PyNone in the code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Return first index of value.

Raises ValueError if the value is not present.
A callable 'key' can be provided to transform each item before comparison with 'value'.
[clinic start generated code]*/

static PyObject *
list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start,
Py_ssize_t stop)
/*[clinic end generated code: output=ec51b88787e4e481 input=40ec5826303a0eb1]*/
Py_ssize_t stop, PyObject *key)
/*[clinic end generated code: output=23a18266d9a3a9b3 input=7115b8d01b704c54]*/
{
Py_ssize_t i;

if (start < 0) {
start += Py_SIZE(self);
if (start < 0)
if (start < 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen other comments, but I have an oposite opinion: diff should be as minimal as possible. This is just a comment, no action needed, unless others also agree :)

@kristjanvalur kristjanvalur Jan 8, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I respectfully disagree, the style should be consistent within the function. Either I convert the entire function to pep7, or stick to the existing style. Which should it be? Also, it is a bit hard to try to follow disagreeing opinions of reviewers.

start = 0;
}
}
if (stop < 0) {
stop += Py_SIZE(self);
if (stop < 0)
if (stop < 0) {
stop = 0;
}
}
for (i = start; i < stop && i < Py_SIZE(self); i++) {
PyObject *obj = self->ob_item[i];
Py_INCREF(obj);
PyObject *obj, *item = self->ob_item[i];
/* take reference to item, in case list modified by key or comparison */
Py_INCREF(item);
if (key == NULL) {
obj = item;
}
else {
obj = PyObject_CallFunctionObjArgs(key, item, NULL);
Py_DECREF(item);
if (obj == NULL) {
return NULL;
}
}
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
Py_DECREF(obj);
if (cmp > 0)
if (cmp > 0) {
return PyLong_FromSsize_t(i);
else if (cmp < 0)
}
else if (cmp < 0) {
return NULL;
}
}
PyErr_Format(PyExc_ValueError, "%R is not in list", value);
return NULL;
Expand Down