@@ -110,7 +110,12 @@ def method_c(self):
110110 return "c"
111111
112112 clsdict = _extract_class_dict (C )
113- assert list (clsdict .keys ()) == ["C_CONSTANT" , "__doc__" , "method_c" ]
113+ expected_keys = ["C_CONSTANT" , "__doc__" , "method_c" ]
114+ # New attribute in Python 3.13 beta 1
115+ # https://github.com/python/cpython/pull/118475
116+ if sys .version_info >= (3 , 13 ):
117+ expected_keys .insert (2 , "__firstlineno__" )
118+ assert list (clsdict .keys ()) == expected_keys
114119 assert clsdict ["C_CONSTANT" ] == 43
115120 assert clsdict ["__doc__" ] is None
116121 assert clsdict ["method_c" ](C ()) == C ().method_c ()
@@ -331,6 +336,25 @@ def g():
331336 g = pickle_depickle (f (), protocol = self .protocol )
332337 self .assertEqual (g (), 2 )
333338
339+ def test_class_no_firstlineno_deletion_ (self ):
340+ # `__firstlineno__` is a new attribute of classes introduced in Python 3.13.
341+ # This attribute used to be automatically deleted when unpickling a class as a
342+ # consequence of cloudpickle setting a class's `__module__` attribute at
343+ # unpickling time (see https://github.com/python/cpython/blob/73c152b346a18ed8308e469bdd232698e6cd3a63/Objects/typeobject.c#L1353-L1356).
344+ # This deletion would cause tests like
345+ # `test_deterministic_dynamic_class_attr_ordering_for_chained_pickling` to fail.
346+ # This test makes sure that the attribute `__firstlineno__` is preserved
347+ # across a cloudpickle roundtrip.
348+
349+ class A :
350+ pass
351+
352+ if hasattr (A , "__firstlineno__" ):
353+ A_roundtrip = pickle_depickle (A , protocol = self .protocol )
354+ assert hasattr (A_roundtrip , "__firstlineno__" )
355+ assert A_roundtrip .__firstlineno__ == A .__firstlineno__
356+
357+
334358 def test_dynamically_generated_class_that_uses_super (self ):
335359 class Base :
336360 def method (self ):
@@ -2067,7 +2091,7 @@ class A:
20672091 # If the `__doc__` attribute is defined after some other class
20682092 # attribute, this can cause class attribute ordering changes due to
20692093 # the way we reconstruct the class definition in
2070- # `_make_class_skeleton `, which creates the class and thus its
2094+ # `_make_skeleton_class `, which creates the class and thus its
20712095 # `__doc__` attribute before populating the class attributes.
20722096 class A :
20732097 name = "A"
@@ -2078,7 +2102,7 @@ class A:
20782102
20792103 # If a `__doc__` is defined on the `__init__` method, this can
20802104 # cause ordering changes due to the way we reconstruct the class
2081- # with `_make_class_skeleton `.
2105+ # with `_make_skeleton_class `.
20822106 class A :
20832107 def __init__ (self ):
20842108 """Class definition with explicit __init__"""
@@ -2136,8 +2160,6 @@ def test_dynamic_class_determinist_subworker_tuple_memoization(self):
21362160 # Arguments' tuple is memoized in the main process but not in the
21372161 # subprocess as the tuples do not share the same id in the loaded
21382162 # class.
2139-
2140- # XXX - this does not seem to work, and I am not sure there is an easy fix.
21412163 class A :
21422164 """Class with potential tuple memoization issues."""
21432165
0 commit comments