@@ -420,6 +420,58 @@ def test_partial_genericalias(self):
420420 self .assertEqual (alias .__args__ , (int ,))
421421 self .assertEqual (alias .__parameters__ , ())
422422
423+ # GH-144475: Tests that the partial object does not change until repr finishes
424+ def test_repr_safety_against_reentrant_mutation (self ):
425+ g_partial = None
426+
427+ class Function :
428+ def __init__ (self , name ):
429+ self .name = name
430+
431+ def __call__ (self ):
432+ return None
433+
434+ def __repr__ (self ):
435+ return f"Function({ self .name } )"
436+
437+ class EvilObject :
438+ def __init__ (self ):
439+ self .triggered = False
440+
441+ def __repr__ (self ):
442+ if not self .triggered and g_partial is not None :
443+ self .triggered = True
444+ new_args_tuple = (None ,)
445+ new_keywords_dict = {"keyword" : None }
446+ new_tuple_state = (Function ("new_function" ), new_args_tuple , new_keywords_dict , None )
447+ g_partial .__setstate__ (new_tuple_state )
448+ gc .collect ()
449+ return f"EvilObject"
450+
451+ trigger = EvilObject ()
452+ func = Function ("old_function" )
453+
454+ g_partial = functools .partial (func , None , trigger = trigger )
455+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), None, trigger=EvilObject)" )
456+
457+ trigger .triggered = False
458+ g_partial = functools .partial (func , trigger , arg = None )
459+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), EvilObject, arg=None)" )
460+
461+
462+ trigger .triggered = False
463+ g_partial = functools .partial (func , trigger , None )
464+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), EvilObject, None)" )
465+
466+ trigger .triggered = False
467+ g_partial = functools .partial (func , trigger = trigger , arg = None )
468+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), trigger=EvilObject, arg=None)" )
469+
470+ trigger .triggered = False
471+ g_partial = functools .partial (func , trigger , None , None , None , None , arg = None )
472+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), EvilObject, None, None, None, None, arg=None)" )
473+
474+
423475
424476@unittest .skipUnless (c_functools , 'requires the C _functools module' )
425477class TestPartialC (TestPartial , unittest .TestCase ):
0 commit comments