-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
bpo-28280: Make PyMapping_Keys(), PyMapping_Values() and PyMapping_Items() always return a list #3840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-28280: Make PyMapping_Keys(), PyMapping_Values() and PyMapping_Items() always return a list #3840
Changes from all commits
bd59df1
15c1486
2d4bd68
f01f279
4b5852d
6682e53
47ea6e7
3e81e35
0421b5c
587a9ba
5dc52c1
de2c440
d492b91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Make `PyMapping_Keys()`, `PyMapping_Values()` and `PyMapping_Items()` always | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention that it was possible to return a tuple. |
||
return a `list` (rather than a `list` or a `tuple`). Patch by Oren Milman. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2147,55 +2147,77 @@ PyMapping_HasKey(PyObject *o, PyObject *key) | |
return 0; | ||
} | ||
|
||
/* This function is quite similar to PySequence_Fast(), but specialized to be | ||
a helper for PyMapping_Keys(), PyMapping_Items() and PyMapping_Values(). | ||
*/ | ||
static PyObject * | ||
method_output_as_list(PyObject *o, _Py_Identifier *meth_id) | ||
{ | ||
PyObject *it, *result, *meth_output; | ||
|
||
assert(o != NULL); | ||
meth_output = _PyObject_CallMethodId(o, meth_id, NULL); | ||
if (meth_output == NULL || PyList_CheckExact(meth_output)) { | ||
return meth_output; | ||
} | ||
it = PyObject_GetIter(meth_output); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just calling list(obj)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer to pass meth_output to PySequence_List() rather than iter(meth_output). list_extend() has a fast-path for tuple. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't matter. Calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. list(iter(obj)) is not exactly the same than list(obj). My point on performance was the cost of PySequence_List(iterator) vs PySequence_List(tuple). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add a special case for tuples:
But I think this is a premature optimization. Do you know any class that return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, it's fine. I don't care much. The current code is ok. |
||
if (it == NULL) { | ||
if (PyErr_ExceptionMatches(PyExc_TypeError)) { | ||
PyErr_Format(PyExc_TypeError, | ||
"%.200s.%U() returned a non-iterable (type %.200s)", | ||
Py_TYPE(o)->tp_name, | ||
meth_id->object, | ||
Py_TYPE(meth_output)->tp_name); | ||
} | ||
Py_DECREF(meth_output); | ||
return NULL; | ||
} | ||
Py_DECREF(meth_output); | ||
result = PySequence_List(it); | ||
Py_DECREF(it); | ||
return result; | ||
} | ||
|
||
PyObject * | ||
PyMapping_Keys(PyObject *o) | ||
{ | ||
PyObject *keys; | ||
PyObject *fast; | ||
_Py_IDENTIFIER(keys); | ||
|
||
if (PyDict_CheckExact(o)) | ||
if (o == NULL) { | ||
return null_error(); | ||
} | ||
if (PyDict_CheckExact(o)) { | ||
return PyDict_Keys(o); | ||
keys = _PyObject_CallMethodId(o, &PyId_keys, NULL); | ||
if (keys == NULL) | ||
return NULL; | ||
fast = PySequence_Fast(keys, "o.keys() are not iterable"); | ||
Py_DECREF(keys); | ||
return fast; | ||
} | ||
return method_output_as_list(o, &PyId_keys); | ||
} | ||
|
||
PyObject * | ||
PyMapping_Items(PyObject *o) | ||
{ | ||
PyObject *items; | ||
PyObject *fast; | ||
_Py_IDENTIFIER(items); | ||
|
||
if (PyDict_CheckExact(o)) | ||
if (o == NULL) { | ||
return null_error(); | ||
} | ||
if (PyDict_CheckExact(o)) { | ||
return PyDict_Items(o); | ||
items = _PyObject_CallMethodId(o, &PyId_items, NULL); | ||
if (items == NULL) | ||
return NULL; | ||
fast = PySequence_Fast(items, "o.items() are not iterable"); | ||
Py_DECREF(items); | ||
return fast; | ||
} | ||
return method_output_as_list(o, &PyId_items); | ||
} | ||
|
||
PyObject * | ||
PyMapping_Values(PyObject *o) | ||
{ | ||
PyObject *values; | ||
PyObject *fast; | ||
_Py_IDENTIFIER(values); | ||
|
||
if (PyDict_CheckExact(o)) | ||
if (o == NULL) { | ||
return null_error(); | ||
} | ||
if (PyDict_CheckExact(o)) { | ||
return PyDict_Values(o); | ||
values = _PyObject_CallMethodId(o, &PyId_values, NULL); | ||
if (values == NULL) | ||
return NULL; | ||
fast = PySequence_Fast(values, "o.values() are not iterable"); | ||
Py_DECREF(values); | ||
return fast; | ||
} | ||
return method_output_as_list(o, &PyId_values); | ||
} | ||
|
||
/* isinstance(), issubclass() */ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a need to test this class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its purpose is to test cases in which the methods already return a
list
, i.e. whenPyList_CheckExact(meth_output)
is true.