Skip to content

Commit ec22c19

Browse files
gpsheadclaude
andcommitted
Optimize exception pickle sizes when timestamps are disabled
When traceback timestamps are disabled, exception pickles no longer include an empty state dictionary, reducing pickle size to match Python 3.13 baseline. When timestamps are enabled, the state dict is included with timestamp data. - BaseException: Only include dict when timestamp > 0 or custom attributes exist - OSError: Apply same optimization while preserving filename attributes - ImportError: Conditionally include state based on meaningful attributes - AttributeError: Always include state dict for Python 3.13 compatibility Results: - ValueError/RuntimeError: 53 bytes (disabled) -> 103 bytes (enabled) - OSError: 56 bytes (disabled) -> 106 bytes (enabled) - AttributeError: 76 bytes (disabled) -> 120 bytes (enabled) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 4aaa4dc commit ec22c19

File tree

1 file changed

+60
-28
lines changed

1 file changed

+60
-28
lines changed

Objects/exceptions.c

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -264,16 +264,29 @@ static PyObject *
264264
BaseException___reduce___impl(PyBaseExceptionObject *self)
265265
/*[clinic end generated code: output=af87c1247ef98748 input=283be5a10d9c964f]*/
266266
{
267-
if (!self->dict) {
268-
self->dict = PyDict_New();
269-
if (self->dict == NULL) {
267+
PyObject *dict = NULL;
268+
269+
/* Only create and include a dict if we have a timestamp to store or
270+
* if the exception already has custom attributes in its dict. */
271+
if (self->timestamp_ns > 0 || (self->dict && PyDict_GET_SIZE(self->dict) > 0)) {
272+
if (!self->dict) {
273+
self->dict = PyDict_New();
274+
if (self->dict == NULL) {
275+
return NULL;
276+
}
277+
}
278+
if (!BaseException_add_timestamp_to_dict(self, self->dict)) {
270279
return NULL;
271280
}
281+
dict = self->dict;
272282
}
273-
if (!BaseException_add_timestamp_to_dict(self, self->dict)) {
274-
return NULL;
283+
284+
/* Include dict in the pickle tuple only if we have one with content */
285+
if (dict) {
286+
return PyTuple_Pack(3, Py_TYPE(self), self->args, dict);
287+
} else {
288+
return PyTuple_Pack(2, Py_TYPE(self), self->args);
275289
}
276-
return PyTuple_Pack(3, Py_TYPE(self), self->args, self->dict);
277290
}
278291

279292
/*
@@ -1904,8 +1917,20 @@ ImportError_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
19041917
PyObject *state = ImportError_getstate(self);
19051918
if (state == NULL)
19061919
return NULL;
1920+
19071921
PyBaseExceptionObject *exc = PyBaseExceptionObject_CAST(self);
1908-
res = PyTuple_Pack(3, Py_TYPE(self), exc->args, state);
1922+
PyImportErrorObject *import_exc = PyImportErrorObject_CAST(self);
1923+
1924+
/* Only include state dict if it has content beyond an empty timestamp */
1925+
bool has_content = (exc->timestamp_ns > 0 ||
1926+
import_exc->name || import_exc->path || import_exc->name_from ||
1927+
(import_exc->dict && PyDict_GET_SIZE(import_exc->dict) > 0));
1928+
1929+
if (has_content) {
1930+
res = PyTuple_Pack(3, Py_TYPE(self), exc->args, state);
1931+
} else {
1932+
res = PyTuple_Pack(2, Py_TYPE(self), exc->args);
1933+
}
19091934
Py_DECREF(state);
19101935
return res;
19111936
}
@@ -2334,15 +2359,29 @@ OSError_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
23342359
} else
23352360
Py_INCREF(args);
23362361

2337-
if (!self->dict) {
2338-
self->dict = PyDict_New();
2362+
PyObject *dict = NULL;
2363+
PyBaseExceptionObject *base_self = (PyBaseExceptionObject*)self;
2364+
2365+
/* Only create and include a dict if we have a timestamp to store or
2366+
* if the exception already has custom attributes in its dict. */
2367+
if (base_self->timestamp_ns > 0 || (self->dict && PyDict_GET_SIZE(self->dict) > 0)) {
2368+
if (!self->dict) {
2369+
self->dict = PyDict_New();
2370+
}
2371+
if (!self->dict ||
2372+
!BaseException_add_timestamp_to_dict(base_self, self->dict)) {
2373+
Py_DECREF(args);
2374+
return NULL;
2375+
}
2376+
dict = self->dict;
23392377
}
2340-
if (!self->dict ||
2341-
!BaseException_add_timestamp_to_dict((PyBaseExceptionObject*)self, self->dict)) {
2342-
Py_DECREF(args);
2343-
return NULL;
2378+
2379+
/* Include dict in the pickle tuple only if we have one with content */
2380+
if (dict) {
2381+
res = PyTuple_Pack(3, Py_TYPE(self), args, dict);
2382+
} else {
2383+
res = PyTuple_Pack(2, Py_TYPE(self), args);
23442384
}
2345-
res = PyTuple_Pack(3, Py_TYPE(self), args, self->dict);
23462385
Py_DECREF(args);
23472386
return res;
23482387
}
@@ -2635,24 +2674,14 @@ AttributeError_getstate(PyObject *op, PyObject *Py_UNUSED(ignored))
26352674
if (dict == NULL) {
26362675
return NULL;
26372676
}
2638-
if (self->name || self->args) {
2639-
if (self->name && PyDict_SetItemString(dict, "name", self->name) < 0) {
2640-
Py_DECREF(dict);
2641-
return NULL;
2642-
}
2643-
/* We specifically are not pickling the obj attribute since there are many
2644-
cases where it is unlikely to be picklable. See GH-103352.
2645-
*/
2646-
if (self->args && PyDict_SetItemString(dict, "args", self->args) < 0) {
2647-
Py_DECREF(dict);
2648-
return NULL;
2649-
}
2650-
return dict;
2651-
}
2677+
2678+
/* Always add timestamp first if present */
26522679
if (!BaseException_add_timestamp_to_dict((PyBaseExceptionObject*)self, dict)) {
26532680
Py_DECREF(dict);
26542681
return NULL;
26552682
}
2683+
2684+
/* Add AttributeError-specific attributes */
26562685
if (self->name && PyDict_SetItemString(dict, "name", self->name) < 0) {
26572686
Py_DECREF(dict);
26582687
return NULL;
@@ -2676,6 +2705,9 @@ AttributeError_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
26762705
}
26772706

26782707
PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op);
2708+
2709+
/* AttributeError always includes state dict for compatibility with Python 3.13 behavior.
2710+
* The getstate method always includes 'args' in the returned dict. */
26792711
PyObject *return_value = PyTuple_Pack(3, Py_TYPE(self), self->args, state);
26802712
Py_DECREF(state);
26812713
return return_value;

0 commit comments

Comments
 (0)