-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
bpo-27015: Save kwargs given to exceptions constructor #11580
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
Changes from 5 commits
6a00f21
12dc912
1d12d28
88ba13e
9986e86
7794862
6c906da
4657d7e
2fc3366
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,3 @@ | ||
| Exceptions now save the keyword arguments given to their constructor in their | ||
| ``kwargs`` attribute. Exceptions with overridden ``__init__`` and using keyword | ||
| arguments are now picklable. Contributed by Rémi Lapeyre. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,20 +41,23 @@ BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
| return NULL; | ||
| /* the dict is created on the fly in PyObject_GenericSetAttr */ | ||
| self->dict = NULL; | ||
| self->kwargs = NULL; | ||
| self->traceback = self->cause = self->context = NULL; | ||
| self->suppress_context = 0; | ||
|
|
||
| if (args) { | ||
| self->args = args; | ||
| Py_INCREF(args); | ||
| return (PyObject *)self; | ||
| } else { | ||
| self->args = PyTuple_New(0); | ||
| if (!self->args) { | ||
| Py_DECREF(self); | ||
| return NULL; | ||
| } | ||
| } | ||
|
|
||
| self->args = PyTuple_New(0); | ||
| if (!self->args) { | ||
| Py_DECREF(self); | ||
| return NULL; | ||
| } | ||
| self->kwargs = kwds; | ||
|
Contributor
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 assignment should only be done if While calls like If
Author
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. Wouldn't it be good to do that for |
||
| Py_XINCREF(kwds); | ||
|
|
||
| return (PyObject *)self; | ||
| } | ||
|
|
@@ -76,6 +79,7 @@ BaseException_clear(PyBaseExceptionObject *self) | |
| { | ||
| Py_CLEAR(self->dict); | ||
| Py_CLEAR(self->args); | ||
| Py_CLEAR(self->kwargs); | ||
| Py_CLEAR(self->traceback); | ||
| Py_CLEAR(self->cause); | ||
| Py_CLEAR(self->context); | ||
|
|
@@ -95,6 +99,7 @@ BaseException_traverse(PyBaseExceptionObject *self, visitproc visit, void *arg) | |
| { | ||
| Py_VISIT(self->dict); | ||
| Py_VISIT(self->args); | ||
| Py_VISIT(self->kwargs); | ||
| Py_VISIT(self->traceback); | ||
| Py_VISIT(self->cause); | ||
| Py_VISIT(self->context); | ||
|
|
@@ -118,21 +123,144 @@ static PyObject * | |
| BaseException_repr(PyBaseExceptionObject *self) | ||
| { | ||
| const char *name = _PyType_Name(Py_TYPE(self)); | ||
| if (PyTuple_GET_SIZE(self->args) == 1) | ||
| return PyUnicode_FromFormat("%s(%R)", name, | ||
| PyTuple_GET_ITEM(self->args, 0)); | ||
| else | ||
| return PyUnicode_FromFormat("%s%R", name, self->args); | ||
| PyObject *separator = NULL; | ||
| PyObject *args = NULL; | ||
| PyObject *kwargs = NULL; | ||
| PyObject *seq = NULL; | ||
| PyObject *repr = NULL; | ||
| PyObject *item = NULL; | ||
| PyObject *items = NULL; | ||
| PyObject *it = NULL; | ||
| PyObject *key = NULL; | ||
| PyObject *value = NULL; | ||
| PyObject *result = NULL; | ||
|
|
||
| separator = PyUnicode_FromString(", "); | ||
|
|
||
| if (PyTuple_Check(self->args)) { | ||
| const Py_ssize_t len = PyTuple_Size(self->args); | ||
| seq = PyTuple_New(len); | ||
| if (seq == NULL) { | ||
| goto fail; | ||
| } | ||
| for (Py_ssize_t i=0; i < len; i++) { | ||
| repr = PyObject_Repr(PyTuple_GET_ITEM(self->args, i)); | ||
| if (repr == NULL) { | ||
| goto fail; | ||
| } | ||
| PyTuple_SET_ITEM(seq, i, repr); | ||
| } | ||
| args = PyUnicode_Join(separator, seq); | ||
| Py_DECREF(seq); | ||
| } | ||
|
|
||
| if (PyMapping_Check(self->kwargs)) { | ||
| const Py_ssize_t len = PyMapping_Length(self->kwargs); | ||
| if (len == -1) { | ||
| goto fail; | ||
| } | ||
| seq = PyTuple_New(len); | ||
| items = PyMapping_Items(self->kwargs); | ||
| if (seq == NULL || items == NULL) { | ||
| goto fail; | ||
| } | ||
| it = PyObject_GetIter(items); | ||
| if (it == NULL) { | ||
| goto fail; | ||
| } | ||
| Py_ssize_t i = 0; | ||
| while ((item = PyIter_Next(it)) != NULL) { | ||
| if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) { | ||
| PyErr_SetString(PyExc_ValueError, "items must return 2-tuples"); | ||
| goto fail; | ||
| } | ||
| key = PyTuple_GET_ITEM(item, 0); | ||
| value = PyTuple_GET_ITEM(item, 1); | ||
| PyTuple_SET_ITEM(seq, i, PyUnicode_FromFormat("%S=%R", key, value)); | ||
|
Member
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. Unless I am missing something
Author
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 is no mention that Should I add a note about this? |
||
| i++; | ||
| Py_DECREF(item); | ||
| } | ||
| kwargs = PyUnicode_Join(separator, seq); | ||
| Py_DECREF(seq); | ||
| Py_DECREF(items); | ||
| Py_DECREF(it); | ||
| } | ||
| Py_DECREF(separator); | ||
|
|
||
| if (args == NULL && kwargs == NULL) { | ||
| result = PyUnicode_FromFormat("%s()", name, kwargs); | ||
|
remilapeyre marked this conversation as resolved.
|
||
| } else if (kwargs == NULL || PyUnicode_GET_LENGTH(kwargs) == 0) { | ||
| result = PyUnicode_FromFormat("%s(%S)", name, args); | ||
| } else if (args == NULL || PyUnicode_GET_LENGTH(args) == 0) { | ||
| result = PyUnicode_FromFormat("%s(%S)", name, kwargs); | ||
| } else { | ||
| result = PyUnicode_FromFormat("%s(%S, %S)", name, args, kwargs); | ||
| } | ||
| Py_XDECREF(args); | ||
| Py_XDECREF(kwargs); | ||
| return result; | ||
|
|
||
| fail: | ||
| Py_XDECREF(separator); | ||
| Py_XDECREF(args); | ||
| Py_XDECREF(kwargs); | ||
| Py_XDECREF(seq); | ||
| Py_XDECREF(repr); | ||
| Py_XDECREF(item); | ||
| Py_XDECREF(items); | ||
| Py_XDECREF(it); | ||
| Py_XDECREF(key); | ||
| Py_XDECREF(value); | ||
| return NULL; | ||
| } | ||
|
|
||
| /* Pickling support */ | ||
| static PyObject * | ||
| BaseException_reduce(PyBaseExceptionObject *self, PyObject *Py_UNUSED(ignored)) | ||
| { | ||
| if (self->args && self->dict) | ||
| return PyTuple_Pack(3, Py_TYPE(self), self->args, self->dict); | ||
| else | ||
| return PyTuple_Pack(2, Py_TYPE(self), self->args); | ||
| PyObject *functools; | ||
| PyObject *partial; | ||
| PyObject *constructor; | ||
| PyObject *args; | ||
| PyObject *result; | ||
| PyObject **newargs; | ||
|
|
||
| _Py_IDENTIFIER(partial); | ||
| functools = PyImport_ImportModule("functools"); | ||
|
Contributor
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 reduce implementation concerns me, as it looks like it will make everything much slower, even for exception instances where Instead, I'd recommend migrating That way the pickle machinery will take care of calling (That would have potential backwards compatibility implications for subclasses implementing reduce based on the parent class implementation, but the same would hold true for introduce a partial object in place of a direct reference to the class - either way, there'll need to be a note in the Porting section of the What's New guide, and switching to
Author
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 tried to do that, I removed static PyObject *
BaseException_getnewargs_ex(PyBaseExceptionObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject *args = PyObject_GetAttrString((PyObject *) self, "args");
PyObject *kwargs = PyObject_GetAttrString((PyObject *) self, "kwargs");
if (args == NULL || kwargs == NULL) {
return NULL;
}
return Py_BuildValue("(OO)", args, kwargs);
}but it brocke pickling. Did I miss something?
Author
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. Oh, found my mistake, using I don't think this happened when using a partial reference on the the constructor of the class.
Author
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. Maybe it's ok to broke pickling support for protocols 0 and 1 since it was broken for keyword args anyway?
Contributor
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. Defining @pitrou Do you have any recommendations here? (Context: trying to get
Author
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. How should I call It seems to me that calling the builtin super is not done anywhere in the source code but I don't find the right way to do it. Do I need to call
Member
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. @ncoghlan Well, I'm not sure why you wouldn't implement the entire logic in Or, rather, you could just define
Contributor
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. @pitrou I only suggested delegating to But if
Author
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. Hi @pitrou @ncoghlan, thanks for you input. I pushed a new commit that implement If I remove the Do I need to define a custom
Author
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 dug further and it seems my issue comes from https://github.com/python/cpython/blob/master/Lib/copyreg.py#L66, I will look into the details tomorrow. |
||
| if (!functools) | ||
|
remilapeyre marked this conversation as resolved.
Outdated
|
||
| return NULL; | ||
| partial = _PyObject_GetAttrId(functools, &PyId_partial); | ||
| Py_DECREF(functools); | ||
| if (!partial) | ||
|
remilapeyre marked this conversation as resolved.
Outdated
|
||
| return NULL; | ||
|
|
||
| Py_ssize_t len = 1; | ||
| if (PyTuple_Check(self->args)) { | ||
| len += PyTuple_GET_SIZE(self->args); | ||
| } | ||
| newargs = PyMem_RawMalloc(len*sizeof(PyObject*)); | ||
|
remilapeyre marked this conversation as resolved.
Outdated
|
||
| newargs[0] = (PyObject *)Py_TYPE(self); | ||
|
|
||
| for (Py_ssize_t i=1; i < len; i++) { | ||
| newargs[i] = PyTuple_GetItem(self->args, i-1); | ||
| } | ||
| constructor = _PyObject_FastCallDict(partial, newargs, len, self->kwargs); | ||
| PyMem_RawFree(newargs); | ||
|
|
||
| Py_DECREF(partial); | ||
|
|
||
| args = PyTuple_New(0); | ||
|
remilapeyre marked this conversation as resolved.
Outdated
|
||
| if (!args) { | ||
| return NULL; | ||
| } | ||
| if (self->args && self->dict){ | ||
| result = PyTuple_Pack(3, constructor, args, self->dict); | ||
| } else { | ||
| result = PyTuple_Pack(2, constructor, args); | ||
| } | ||
| Py_DECREF(constructor); | ||
| Py_DECREF(args); | ||
| return result; | ||
| } | ||
|
|
||
| /* | ||
|
|
@@ -206,6 +334,26 @@ BaseException_set_args(PyBaseExceptionObject *self, PyObject *val, void *Py_UNUS | |
| return 0; | ||
| } | ||
|
|
||
| static PyObject * | ||
| BaseException_get_kwargs(PyBaseExceptionObject *self, void *Py_UNUSED(ignored)) { | ||
| if (self->kwargs == NULL) { | ||
| self->kwargs = PyDict_New(); | ||
|
remilapeyre marked this conversation as resolved.
|
||
| } | ||
| Py_XINCREF(self->kwargs); | ||
| return self->kwargs; | ||
| } | ||
|
|
||
| static int | ||
| BaseException_set_kwargs(PyBaseExceptionObject *self, PyObject *val, void *Py_UNUSED(ignored)) { | ||
| if (val == NULL) { | ||
| PyErr_SetString(PyExc_TypeError, "kwargs may not be deleted"); | ||
| return -1; | ||
| } | ||
|
Contributor
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.
Author
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 thought about doing that but it seems to me that since tuples support slicing? If so, is the check still worth it? |
||
| Py_INCREF(val); | ||
| self->kwargs = val; | ||
|
remilapeyre marked this conversation as resolved.
Outdated
|
||
| return 0; | ||
| } | ||
|
|
||
| static PyObject * | ||
| BaseException_get_tb(PyBaseExceptionObject *self, void *Py_UNUSED(ignored)) | ||
| { | ||
|
|
@@ -296,6 +444,7 @@ BaseException_set_cause(PyObject *self, PyObject *arg, void *Py_UNUSED(ignored)) | |
| static PyGetSetDef BaseException_getset[] = { | ||
| {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict}, | ||
| {"args", (getter)BaseException_get_args, (setter)BaseException_set_args}, | ||
| {"kwargs", (getter)BaseException_get_kwargs, (setter)BaseException_set_kwargs}, | ||
| {"__traceback__", (getter)BaseException_get_tb, (setter)BaseException_set_tb}, | ||
| {"__context__", BaseException_get_context, | ||
| BaseException_set_context, PyDoc_STR("exception context")}, | ||
|
|
||
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.
Typo: dictionnary -> dictionary