@@ -514,6 +514,58 @@ def test_partial_genericalias(self):
514514 self .assertEqual (alias .__args__ , (int ,))
515515 self .assertEqual (alias .__parameters__ , ())
516516
517+ # GH-144475: Tests that the partial object does not change until repr finishes
518+ def test_repr_safety_against_reentrant_mutation (self ):
519+ g_partial = None
520+
521+ class Function :
522+ def __init__ (self , name ):
523+ self .name = name
524+
525+ def __call__ (self ):
526+ return None
527+
528+ def __repr__ (self ):
529+ return f"Function({ self .name } )"
530+
531+ class EvilObject :
532+ def __init__ (self ):
533+ self .triggered = False
534+
535+ def __repr__ (self ):
536+ if not self .triggered and g_partial is not None :
537+ self .triggered = True
538+ new_args_tuple = (None ,)
539+ new_keywords_dict = {"keyword" : None }
540+ new_tuple_state = (Function ("new_function" ), new_args_tuple , new_keywords_dict , None )
541+ g_partial .__setstate__ (new_tuple_state )
542+ gc .collect ()
543+ return f"EvilObject"
544+
545+ trigger = EvilObject ()
546+ func = Function ("old_function" )
547+
548+ g_partial = functools .partial (func , None , trigger = trigger )
549+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), None, trigger=EvilObject)" )
550+
551+ trigger .triggered = False
552+ g_partial = functools .partial (func , trigger , arg = None )
553+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), EvilObject, arg=None)" )
554+
555+
556+ trigger .triggered = False
557+ g_partial = functools .partial (func , trigger , None )
558+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), EvilObject, None)" )
559+
560+ trigger .triggered = False
561+ g_partial = functools .partial (func , trigger = trigger , arg = None )
562+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), trigger=EvilObject, arg=None)" )
563+
564+ trigger .triggered = False
565+ g_partial = functools .partial (func , trigger , None , None , None , None , arg = None )
566+ self .assertEqual (repr (g_partial ),"functools.partial(Function(old_function), EvilObject, None, None, None, None, arg=None)" )
567+
568+
517569
518570@unittest .skipUnless (c_functools , 'requires the C _functools module' )
519571class TestPartialC (TestPartial , unittest .TestCase ):
0 commit comments