bpo-28280: Make PyMapping_Keys(), PyMapping_Values() and PyMapping_Items() always return a list#3840
Conversation
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Add also an entry in What's New. This will help people to revise their code and fix errors in old versions of Python.
| return *NULL*. | ||
| On success, return a list of the keys in object *o*. On failure, return | ||
| *NULL*. | ||
|
|
There was a problem hiding this comment.
Add a "versionchanged" directive.
This is needed for users that write a code that can be used with older Python.
| self.assertIn(b'MemoryError 3 30', out) | ||
|
|
||
| def test_mapping_keys_values_items(self): | ||
| class Mapping1(dict): |
There was a problem hiding this comment.
Is there a need to test this class?
There was a problem hiding this comment.
Its purpose is to test cases in which the methods already return a list, i.e. when PyList_CheckExact(meth_output) is true.
| @@ -0,0 +1,2 @@ | |||
| Make `PyMapping_Keys()`, `PyMapping_Values()` and `PyMapping_Items()` always | |||
There was a problem hiding this comment.
Mention that it was possible to return a tuple.
| PyErr_Format(PyExc_TypeError, | ||
| "%.200s.%s() must return an iterable", | ||
| Py_TYPE(o)->tp_name, | ||
| meth_id->string); |
There was a problem hiding this comment.
Use meth_id->string and the %U format. The result of formatting is a PyUnicode object.
There was a problem hiding this comment.
I don't think i understand your comment..
meth_id->string is of type const char*, so when i use %U, it causes a crash.
There was a problem hiding this comment.
There is a redundant call of PyUnicode_DecodeUTF8Stateful() inside of PyUnicode_FromFormatV called from PyErr_Format(). It can be avoided by passing a PyUnicode object (meth_id->string) and using the %U format.
There was a problem hiding this comment.
IIUC, you suggest something like:
PyErr_Format(PyExc_TypeError,
"%.200s.%U() returned a non-iterable (type %.200s)",
Py_TYPE(o)->tp_name,
PyUnicode_FromString(meth_id->string),
Py_TYPE(meth_output)->tp_name);
But then i would also have to handle refcounts, and the case in which PyUnicode_FromString() failed. Do you think it's worth it?
There was a problem hiding this comment.
No, I suggest to use meth_id->object which is a cached PyUnicode_FromString(meth_id->string).
There was a problem hiding this comment.
Ah, I just have noticed typos in my previous comments! Of course I meant meth_id->object rather of meth_id->string!
| if (it == NULL) { | ||
| if (PyErr_ExceptionMatches(PyExc_TypeError)) { | ||
| PyErr_Format(PyExc_TypeError, | ||
| "%.200s.%s() must return an iterable", |
There was a problem hiding this comment.
Add an actual type of meth_output.
| } | ||
|
|
||
| static PyObject * | ||
| method_output_as_list(PyObject *o, _Py_Identifier *meth_id) |
There was a problem hiding this comment.
Add a comment that this is mainly a copy of PySequence_Fast.
…hanged directives, and improved the error message.
|
|
||
| * The result of :c:func:`PyMapping_Keys`, :c:func:`PyMapping_Values` and | ||
| :c:func:`PyMapping_Items` is now always a list, rather than a list or a | ||
| tuple. (Contributed by Serhiy Storchaka and Oren Milman in :issue:`28280`.) |
There was a problem hiding this comment.
No need to mention my name. I'm just a reviewer.
There was a problem hiding this comment.
You are the author of the issue in bpo, but as you wish.
There was a problem hiding this comment.
The one who opened an issue is not the one who fixed it.
serhiy-storchaka
left a comment
There was a problem hiding this comment.
I'm approving this PR, but since I have proposed this change I can miss flaws of it. I want to get the approve from other core developers.
| if (meth_output == NULL || PyList_CheckExact(meth_output)) { | ||
| return meth_output; | ||
| } | ||
| it = PyObject_GetIter(meth_output); |
There was a problem hiding this comment.
Why not just calling list(obj)?
There was a problem hiding this comment.
PySequence_List() few lines below is just a C API equivalent of calling list(). But first we want to produce better error message if keys() returns non-iterable. It is in the current code, and removing it will simplify the code, but introduce a regression.
There was a problem hiding this comment.
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.
This doesn't matter. keys() returning tuple should be very rare. I don't know any example.
Calling PyObject_GetIter() on an iterator is cheap, it should return the iterator itself. But calling PyObject_GetIter() on a general iterable creates a new iterator object. This is not so cheap. This will add an overhead (and possible side effects) in most common cases.
There was a problem hiding this comment.
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.
We could add a special case for tuples:
if (PyTuple_CheckExact(meth_output)) {
result = PySequence_List(meth_output);
Py_DECREF(meth_output);
return result;
}
But I think this is a premature optimization. Do you know any class that return keys() as a tuple?
There was a problem hiding this comment.
Nah, it's fine. I don't care much. The current code is ok.
In addition:
get_mapping_keys(),get_mapping_values()andget_mapping_items()to_testcapi, so that we would be able to testPyMapping_Keys(),PyMapping_Values()andPyMapping_Items().test_capito verify that each of these functions returns the appropriate list, and that it raises the appropriate error in case of a bad argument.https://bugs.python.org/issue28280