Issue #22955: attrgetter, itemgetter and methodcaller objects in the operator

module now support pickling.  Added readable and evaluable repr for these
objects.  Based on patch by Josh Rosenberg.
This commit is contained in:
Serhiy Storchaka 2015-05-20 18:29:18 +03:00
parent 5418d0bfc4
commit 35ac5f8280
4 changed files with 427 additions and 9 deletions

View file

@ -485,6 +485,41 @@ itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw)
return result;
}
static PyObject *
itemgetter_repr(itemgetterobject *ig)
{
PyObject *repr;
const char *reprfmt;
int status = Py_ReprEnter((PyObject *)ig);
if (status != 0) {
if (status < 0)
return NULL;
return PyUnicode_FromFormat("%s(...)", Py_TYPE(ig)->tp_name);
}
reprfmt = ig->nitems == 1 ? "%s(%R)" : "%s%R";
repr = PyUnicode_FromFormat(reprfmt, Py_TYPE(ig)->tp_name, ig->item);
Py_ReprLeave((PyObject *)ig);
return repr;
}
static PyObject *
itemgetter_reduce(itemgetterobject *ig)
{
if (ig->nitems == 1)
return Py_BuildValue("O(O)", Py_TYPE(ig), ig->item);
return PyTuple_Pack(2, Py_TYPE(ig), ig->item);
}
PyDoc_STRVAR(reduce_doc, "Return state information for pickling");
static PyMethodDef itemgetter_methods[] = {
{"__reduce__", (PyCFunction)itemgetter_reduce, METH_NOARGS,
reduce_doc},
{NULL}
};
PyDoc_STRVAR(itemgetter_doc,
"itemgetter(item, ...) --> itemgetter object\n\
\n\
@ -503,7 +538,7 @@ static PyTypeObject itemgetter_type = {
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
(reprfunc)itemgetter_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
@ -521,7 +556,7 @@ static PyTypeObject itemgetter_type = {
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
itemgetter_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
@ -737,6 +772,91 @@ attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw)
return result;
}
static PyObject *
dotjoinattr(PyObject *attr, PyObject **attrsep)
{
if (PyTuple_CheckExact(attr)) {
if (*attrsep == NULL) {
*attrsep = PyUnicode_FromString(".");
if (*attrsep == NULL)
return NULL;
}
return PyUnicode_Join(*attrsep, attr);
} else {
Py_INCREF(attr);
return attr;
}
}
static PyObject *
attrgetter_args(attrgetterobject *ag)
{
Py_ssize_t i;
PyObject *attrsep = NULL;
PyObject *attrstrings = PyTuple_New(ag->nattrs);
if (attrstrings == NULL)
return NULL;
for (i = 0; i < ag->nattrs; ++i) {
PyObject *attr = PyTuple_GET_ITEM(ag->attr, i);
PyObject *attrstr = dotjoinattr(attr, &attrsep);
if (attrstr == NULL) {
Py_XDECREF(attrsep);
Py_DECREF(attrstrings);
return NULL;
}
PyTuple_SET_ITEM(attrstrings, i, attrstr);
}
Py_XDECREF(attrsep);
return attrstrings;
}
static PyObject *
attrgetter_repr(attrgetterobject *ag)
{
PyObject *repr = NULL;
int status = Py_ReprEnter((PyObject *)ag);
if (status != 0) {
if (status < 0)
return NULL;
return PyUnicode_FromFormat("%s(...)", Py_TYPE(ag)->tp_name);
}
if (ag->nattrs == 1) {
PyObject *attrsep = NULL;
PyObject *attr = dotjoinattr(PyTuple_GET_ITEM(ag->attr, 0), &attrsep);
if (attr != NULL)
repr = PyUnicode_FromFormat("%s(%R)", Py_TYPE(ag)->tp_name, attr);
Py_XDECREF(attrsep);
}
else {
PyObject *attrstrings = attrgetter_args(ag);
if (attrstrings != NULL) {
repr = PyUnicode_FromFormat("%s%R",
Py_TYPE(ag)->tp_name, attrstrings);
Py_DECREF(attrstrings);
}
}
Py_ReprLeave((PyObject *)ag);
return repr;
}
static PyObject *
attrgetter_reduce(attrgetterobject *ag)
{
PyObject *attrstrings = attrgetter_args(ag);
if (attrstrings == NULL)
return NULL;
return Py_BuildValue("ON", Py_TYPE(ag), attrstrings);
}
static PyMethodDef attrgetter_methods[] = {
{"__reduce__", (PyCFunction)attrgetter_reduce, METH_NOARGS,
reduce_doc},
{NULL}
};
PyDoc_STRVAR(attrgetter_doc,
"attrgetter(attr, ...) --> attrgetter object\n\
\n\
@ -757,7 +877,7 @@ static PyTypeObject attrgetter_type = {
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
(reprfunc)attrgetter_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
@ -775,7 +895,7 @@ static PyTypeObject attrgetter_type = {
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
attrgetter_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
@ -813,6 +933,13 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return NULL;
}
name = PyTuple_GET_ITEM(args, 0);
if (!PyUnicode_Check(name)) {
PyErr_SetString(PyExc_TypeError,
"method name must be a string");
return NULL;
}
/* create methodcallerobject structure */
mc = PyObject_GC_New(methodcallerobject, &methodcaller_type);
if (mc == NULL)
@ -825,8 +952,8 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
}
mc->args = newargs;
name = PyTuple_GET_ITEM(args, 0);
Py_INCREF(name);
PyUnicode_InternInPlace(&name);
mc->name = name;
Py_XINCREF(kwds);
@ -869,6 +996,142 @@ methodcaller_call(methodcallerobject *mc, PyObject *args, PyObject *kw)
return result;
}
static PyObject *
methodcaller_repr(methodcallerobject *mc)
{
PyObject *argreprs, *repr = NULL, *sep, *joinedargreprs;
Py_ssize_t numtotalargs, numposargs, numkwdargs, i;
int status = Py_ReprEnter((PyObject *)mc);
if (status != 0) {
if (status < 0)
return NULL;
return PyUnicode_FromFormat("%s(...)", Py_TYPE(mc)->tp_name);
}
if (mc->kwds != NULL) {
numkwdargs = PyDict_Size(mc->kwds);
if (numkwdargs < 0) {
Py_ReprLeave((PyObject *)mc);
return NULL;
}
} else {
numkwdargs = 0;
}
numposargs = PyTuple_GET_SIZE(mc->args);
numtotalargs = numposargs + numkwdargs;
if (numtotalargs == 0) {
repr = PyUnicode_FromFormat("%s(%R)", Py_TYPE(mc)->tp_name, mc->name);
Py_ReprLeave((PyObject *)mc);
return repr;
}
argreprs = PyTuple_New(numtotalargs);
if (argreprs == NULL) {
Py_ReprLeave((PyObject *)mc);
return NULL;
}
for (i = 0; i < numposargs; ++i) {
PyObject *onerepr = PyObject_Repr(PyTuple_GET_ITEM(mc->args, i));
if (onerepr == NULL)
goto done;
PyTuple_SET_ITEM(argreprs, i, onerepr);
}
if (numkwdargs != 0) {
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(mc->kwds, &pos, &key, &value)) {
PyObject *onerepr = PyUnicode_FromFormat("%U=%R", key, value);
if (onerepr == NULL)
goto done;
if (i >= numtotalargs) {
i = -1;
break;
}
PyTuple_SET_ITEM(argreprs, i, onerepr);
++i;
}
if (i != numtotalargs) {
PyErr_SetString(PyExc_RuntimeError,
"keywords dict changed size during iteration");
goto done;
}
}
sep = PyUnicode_FromString(", ");
if (sep == NULL)
goto done;
joinedargreprs = PyUnicode_Join(sep, argreprs);
Py_DECREF(sep);
if (joinedargreprs == NULL)
goto done;
repr = PyUnicode_FromFormat("%s(%R, %U)", Py_TYPE(mc)->tp_name,
mc->name, joinedargreprs);
Py_DECREF(joinedargreprs);
done:
Py_DECREF(argreprs);
Py_ReprLeave((PyObject *)mc);
return repr;
}
static PyObject *
methodcaller_reduce(methodcallerobject *mc)
{
PyObject *newargs;
if (!mc->kwds || PyDict_Size(mc->kwds) == 0) {
Py_ssize_t i;
Py_ssize_t callargcount = PyTuple_GET_SIZE(mc->args);
newargs = PyTuple_New(1 + callargcount);
if (newargs == NULL)
return NULL;
Py_INCREF(mc->name);
PyTuple_SET_ITEM(newargs, 0, mc->name);
for (i = 0; i < callargcount; ++i) {
PyObject *arg = PyTuple_GET_ITEM(mc->args, i);
Py_INCREF(arg);
PyTuple_SET_ITEM(newargs, i + 1, arg);
}
return Py_BuildValue("ON", Py_TYPE(mc), newargs);
}
else {
PyObject *functools;
PyObject *partial;
PyObject *constructor;
_Py_IDENTIFIER(partial);
functools = PyImport_ImportModule("functools");
if (!functools)
return NULL;
partial = _PyObject_GetAttrId(functools, &PyId_partial);
Py_DECREF(functools);
if (!partial)
return NULL;
newargs = PyTuple_New(2);
if (newargs == NULL) {
Py_DECREF(partial);
return NULL;
}
Py_INCREF(Py_TYPE(mc));
PyTuple_SET_ITEM(newargs, 0, (PyObject *)Py_TYPE(mc));
Py_INCREF(mc->name);
PyTuple_SET_ITEM(newargs, 1, mc->name);
constructor = PyObject_Call(partial, newargs, mc->kwds);
Py_DECREF(newargs);
Py_DECREF(partial);
return Py_BuildValue("NO", constructor, mc->args);
}
}
static PyMethodDef methodcaller_methods[] = {
{"__reduce__", (PyCFunction)methodcaller_reduce, METH_NOARGS,
reduce_doc},
{NULL}
};
PyDoc_STRVAR(methodcaller_doc,
"methodcaller(name, ...) --> methodcaller object\n\
\n\
@ -888,7 +1151,7 @@ static PyTypeObject methodcaller_type = {
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
(reprfunc)methodcaller_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
@ -906,7 +1169,7 @@ static PyTypeObject methodcaller_type = {
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
methodcaller_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */