mirror of https://github.com/python/cpython
bpo-28280: Make PyMapping_Keys(), PyMapping_Values() and PyMapping_Items() always return a list (#3840)
This commit is contained in:
parent
f07e2b64df
commit
0ccc0f6c74
|
@ -50,20 +50,29 @@ Mapping Protocol
|
||||||
|
|
||||||
.. c:function:: PyObject* PyMapping_Keys(PyObject *o)
|
.. c:function:: PyObject* PyMapping_Keys(PyObject *o)
|
||||||
|
|
||||||
On success, return a list or tuple of the keys in object *o*. On failure,
|
On success, return a list of the keys in object *o*. On failure, return
|
||||||
return *NULL*.
|
*NULL*.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.7
|
||||||
|
Previously, the function returned a list or a tuple.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: PyObject* PyMapping_Values(PyObject *o)
|
.. c:function:: PyObject* PyMapping_Values(PyObject *o)
|
||||||
|
|
||||||
On success, return a list or tuple of the values in object *o*. On failure,
|
On success, return a list of the values in object *o*. On failure, return
|
||||||
return *NULL*.
|
*NULL*.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.7
|
||||||
|
Previously, the function returned a list or a tuple.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: PyObject* PyMapping_Items(PyObject *o)
|
.. c:function:: PyObject* PyMapping_Items(PyObject *o)
|
||||||
|
|
||||||
On success, return a list or tuple of the items in object *o*, where each item
|
On success, return a list of the items in object *o*, where each item is a
|
||||||
is a tuple containing a key-value pair. On failure, return *NULL*.
|
tuple containing a key-value pair. On failure, return *NULL*.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.7
|
||||||
|
Previously, the function returned a list or a tuple.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: PyObject* PyMapping_GetItemString(PyObject *o, const char *key)
|
.. c:function:: PyObject* PyMapping_GetItemString(PyObject *o, const char *key)
|
||||||
|
|
|
@ -404,6 +404,10 @@ Build and C API Changes
|
||||||
is now of type ``const char *`` rather of ``char *``. (Contributed by Serhiy
|
is now of type ``const char *`` rather of ``char *``. (Contributed by Serhiy
|
||||||
Storchaka in :issue:`28769`.)
|
Storchaka in :issue:`28769`.)
|
||||||
|
|
||||||
|
* 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 Oren Milman in :issue:`28280`.)
|
||||||
|
|
||||||
* Added functions :c:func:`PySlice_Unpack` and :c:func:`PySlice_AdjustIndices`.
|
* Added functions :c:func:`PySlice_Unpack` and :c:func:`PySlice_AdjustIndices`.
|
||||||
(Contributed by Serhiy Storchaka in :issue:`27867`.)
|
(Contributed by Serhiy Storchaka in :issue:`27867`.)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Run the _testcapi module tests (tests for the Python/C API): by defn,
|
# Run the _testcapi module tests (tests for the Python/C API): by defn,
|
||||||
# these are all functions _testcapi exports whose name begins with 'test_'.
|
# these are all functions _testcapi exports whose name begins with 'test_'.
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple, OrderedDict
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import platform
|
import platform
|
||||||
|
@ -272,6 +272,50 @@ class CAPITest(unittest.TestCase):
|
||||||
self.assertIn(b'MemoryError 2 20', out)
|
self.assertIn(b'MemoryError 2 20', out)
|
||||||
self.assertIn(b'MemoryError 3 30', out)
|
self.assertIn(b'MemoryError 3 30', out)
|
||||||
|
|
||||||
|
def test_mapping_keys_values_items(self):
|
||||||
|
class Mapping1(dict):
|
||||||
|
def keys(self):
|
||||||
|
return list(super().keys())
|
||||||
|
def values(self):
|
||||||
|
return list(super().values())
|
||||||
|
def items(self):
|
||||||
|
return list(super().items())
|
||||||
|
class Mapping2(dict):
|
||||||
|
def keys(self):
|
||||||
|
return tuple(super().keys())
|
||||||
|
def values(self):
|
||||||
|
return tuple(super().values())
|
||||||
|
def items(self):
|
||||||
|
return tuple(super().items())
|
||||||
|
dict_obj = {'foo': 1, 'bar': 2, 'spam': 3}
|
||||||
|
|
||||||
|
for mapping in [{}, OrderedDict(), Mapping1(), Mapping2(),
|
||||||
|
dict_obj, OrderedDict(dict_obj),
|
||||||
|
Mapping1(dict_obj), Mapping2(dict_obj)]:
|
||||||
|
self.assertListEqual(_testcapi.get_mapping_keys(mapping),
|
||||||
|
list(mapping.keys()))
|
||||||
|
self.assertListEqual(_testcapi.get_mapping_values(mapping),
|
||||||
|
list(mapping.values()))
|
||||||
|
self.assertListEqual(_testcapi.get_mapping_items(mapping),
|
||||||
|
list(mapping.items()))
|
||||||
|
|
||||||
|
def test_mapping_keys_values_items_bad_arg(self):
|
||||||
|
self.assertRaises(AttributeError, _testcapi.get_mapping_keys, None)
|
||||||
|
self.assertRaises(AttributeError, _testcapi.get_mapping_values, None)
|
||||||
|
self.assertRaises(AttributeError, _testcapi.get_mapping_items, None)
|
||||||
|
|
||||||
|
class BadMapping:
|
||||||
|
def keys(self):
|
||||||
|
return None
|
||||||
|
def values(self):
|
||||||
|
return None
|
||||||
|
def items(self):
|
||||||
|
return None
|
||||||
|
bad_mapping = BadMapping()
|
||||||
|
self.assertRaises(TypeError, _testcapi.get_mapping_keys, bad_mapping)
|
||||||
|
self.assertRaises(TypeError, _testcapi.get_mapping_values, bad_mapping)
|
||||||
|
self.assertRaises(TypeError, _testcapi.get_mapping_items, bad_mapping)
|
||||||
|
|
||||||
|
|
||||||
class TestPendingCalls(unittest.TestCase):
|
class TestPendingCalls(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Make `PyMapping_Keys()`, `PyMapping_Values()` and `PyMapping_Items()` always
|
||||||
|
return a `list` (rather than a `list` or a `tuple`). Patch by Oren Milman.
|
|
@ -4306,6 +4306,25 @@ py_w_stopcode(PyObject *self, PyObject *args)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
get_mapping_keys(PyObject* self, PyObject *obj)
|
||||||
|
{
|
||||||
|
return PyMapping_Keys(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
get_mapping_values(PyObject* self, PyObject *obj)
|
||||||
|
{
|
||||||
|
return PyMapping_Values(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
get_mapping_items(PyObject* self, PyObject *obj)
|
||||||
|
{
|
||||||
|
return PyMapping_Items(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
test_pythread_tss_key_state(PyObject *self, PyObject *args)
|
test_pythread_tss_key_state(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
|
@ -4573,6 +4592,9 @@ static PyMethodDef TestMethods[] = {
|
||||||
#ifdef W_STOPCODE
|
#ifdef W_STOPCODE
|
||||||
{"W_STOPCODE", py_w_stopcode, METH_VARARGS},
|
{"W_STOPCODE", py_w_stopcode, METH_VARARGS},
|
||||||
#endif
|
#endif
|
||||||
|
{"get_mapping_keys", get_mapping_keys, METH_O},
|
||||||
|
{"get_mapping_values", get_mapping_values, METH_O},
|
||||||
|
{"get_mapping_items", get_mapping_items, METH_O},
|
||||||
{"test_pythread_tss_key_state", test_pythread_tss_key_state, METH_VARARGS},
|
{"test_pythread_tss_key_state", test_pythread_tss_key_state, METH_VARARGS},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
|
@ -2147,55 +2147,77 @@ PyMapping_HasKey(PyObject *o, PyObject *key)
|
||||||
return 0;
|
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);
|
||||||
|
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 *
|
PyObject *
|
||||||
PyMapping_Keys(PyObject *o)
|
PyMapping_Keys(PyObject *o)
|
||||||
{
|
{
|
||||||
PyObject *keys;
|
|
||||||
PyObject *fast;
|
|
||||||
_Py_IDENTIFIER(keys);
|
_Py_IDENTIFIER(keys);
|
||||||
|
|
||||||
if (PyDict_CheckExact(o))
|
if (o == NULL) {
|
||||||
|
return null_error();
|
||||||
|
}
|
||||||
|
if (PyDict_CheckExact(o)) {
|
||||||
return PyDict_Keys(o);
|
return PyDict_Keys(o);
|
||||||
keys = _PyObject_CallMethodId(o, &PyId_keys, NULL);
|
}
|
||||||
if (keys == NULL)
|
return method_output_as_list(o, &PyId_keys);
|
||||||
return NULL;
|
|
||||||
fast = PySequence_Fast(keys, "o.keys() are not iterable");
|
|
||||||
Py_DECREF(keys);
|
|
||||||
return fast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyMapping_Items(PyObject *o)
|
PyMapping_Items(PyObject *o)
|
||||||
{
|
{
|
||||||
PyObject *items;
|
|
||||||
PyObject *fast;
|
|
||||||
_Py_IDENTIFIER(items);
|
_Py_IDENTIFIER(items);
|
||||||
|
|
||||||
if (PyDict_CheckExact(o))
|
if (o == NULL) {
|
||||||
|
return null_error();
|
||||||
|
}
|
||||||
|
if (PyDict_CheckExact(o)) {
|
||||||
return PyDict_Items(o);
|
return PyDict_Items(o);
|
||||||
items = _PyObject_CallMethodId(o, &PyId_items, NULL);
|
}
|
||||||
if (items == NULL)
|
return method_output_as_list(o, &PyId_items);
|
||||||
return NULL;
|
|
||||||
fast = PySequence_Fast(items, "o.items() are not iterable");
|
|
||||||
Py_DECREF(items);
|
|
||||||
return fast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyMapping_Values(PyObject *o)
|
PyMapping_Values(PyObject *o)
|
||||||
{
|
{
|
||||||
PyObject *values;
|
|
||||||
PyObject *fast;
|
|
||||||
_Py_IDENTIFIER(values);
|
_Py_IDENTIFIER(values);
|
||||||
|
|
||||||
if (PyDict_CheckExact(o))
|
if (o == NULL) {
|
||||||
|
return null_error();
|
||||||
|
}
|
||||||
|
if (PyDict_CheckExact(o)) {
|
||||||
return PyDict_Values(o);
|
return PyDict_Values(o);
|
||||||
values = _PyObject_CallMethodId(o, &PyId_values, NULL);
|
}
|
||||||
if (values == NULL)
|
return method_output_as_list(o, &PyId_values);
|
||||||
return NULL;
|
|
||||||
fast = PySequence_Fast(values, "o.values() are not iterable");
|
|
||||||
Py_DECREF(values);
|
|
||||||
return fast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* isinstance(), issubclass() */
|
/* isinstance(), issubclass() */
|
||||||
|
|
Loading…
Reference in New Issue