@@ -264,16 +264,29 @@ static PyObject *
264264BaseException___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