@@ -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