@@ -404,6 +404,119 @@ def test_buildvalue_ints(self):
404404 def test_buildvalue_N (self ):
405405 _testcapi .test_buildvalue_N ()
406406
407+ def test_sequence_repeat_subtypes (self ):
408+ # List subtype:
409+ class CustomList (list ): ...
410+
411+ sequence = CustomList (['a' , 'b' ])
412+ repeated_sequence = _testcapi .sequence_repeat (sequence , 3 )
413+ self .assertIs (type (repeated_sequence ), list )
414+ self .assertEqual (repeated_sequence , sequence * 3 )
415+ self .assertEqual (sequence , CustomList (['a' , 'b' ]))
416+
417+ # Tuple subtype:
418+ class CustomTuple (tuple ): ...
419+
420+ sequence = CustomTuple (('a' , 'b' ))
421+ repeated_sequence = _testcapi .sequence_repeat (sequence , 3 )
422+ self .assertIs (type (repeated_sequence ), tuple )
423+ self .assertEqual (repeated_sequence , sequence * 3 )
424+ self .assertEqual (sequence , CustomTuple (('a' , 'b' )))
425+
426+ # String subtype:
427+ class CustomStr (str ): ...
428+
429+ sequence = CustomStr ('abc' )
430+ repeated_sequence = _testcapi .sequence_repeat (sequence , 2 )
431+ self .assertIs (type (repeated_sequence ), str )
432+ self .assertEqual (repeated_sequence , sequence * 2 )
433+ self .assertEqual (sequence , CustomStr ('abc' ))
434+
435+ def test_sequence_repeat_numeric_fallback (self ):
436+ # PySequence_Repeat falls back to nb_multiply (__mul__) when the
437+ # object looks like a sequence but has no sq_repeat slot.
438+ class CustomSequenceWithMul :
439+ def __getitem__ (self , index ):
440+ return index # so that it looks like a sequence
441+
442+ def __mul__ (self , count ):
443+ return f'repeated { count } times'
444+
445+ sequence = CustomSequenceWithMul ()
446+ self .assertEqual (_testcapi .sequence_repeat (sequence , 3 ),
447+ 'repeated 3 times' )
448+
449+ def test_sequence_repeat_errors (self ):
450+ # Corner case, custom subtype returns `NotImplemented`:
451+ class CustomListWithNI (list ):
452+ def __mul__ (self , other ):
453+ return NotImplemented
454+
455+ sequence = CustomListWithNI (['a' , 'b' ])
456+ with self .assertRaises (TypeError ):
457+ _testcapi .sequence_repeat (sequence , 3 )
458+ self .assertEqual (sequence , CustomListWithNI (['a' , 'b' ])) # not changed
459+
460+ # Non-sequences do not work (even with `__mul__`):
461+ class CustomTypeWithMul :
462+ def __mul__ (self , other ):
463+ return 5 # won't be called
464+
465+ not_sequence = CustomTypeWithMul ()
466+ with self .assertRaises (TypeError ):
467+ _testcapi .sequence_repeat (not_sequence , 1 )
468+
469+ def test_sequence_inplace_repeat_subtypes (self ):
470+ # List subtype:
471+ class CustomList (list ): ...
472+
473+ sequence = CustomList (['a' , 'b' ])
474+ repeated_sequence = _testcapi .sequence_inplace_repeat (sequence , 3 )
475+ self .assertIs (repeated_sequence , sequence )
476+ self .assertEqual (repeated_sequence , ['a' , 'b' , 'a' , 'b' , 'a' , 'b' ])
477+
478+ # Tuple subtype:
479+ class CustomTuple (tuple ): ...
480+
481+ sequence = CustomTuple (('a' , 'b' ))
482+ repeated_sequence = _testcapi .sequence_inplace_repeat (sequence , 3 )
483+ self .assertIs (type (repeated_sequence ), tuple )
484+ self .assertEqual (repeated_sequence , sequence * 3 )
485+ self .assertEqual (sequence , CustomTuple (('a' , 'b' )))
486+
487+ # String subtype:
488+ class CustomStr (str ): ...
489+
490+ sequence = CustomStr ('abc' )
491+ repeated_sequence = _testcapi .sequence_inplace_repeat (sequence , 2 )
492+ self .assertIs (type (repeated_sequence ), str )
493+ self .assertEqual (repeated_sequence , sequence * 2 )
494+ self .assertEqual (sequence , CustomStr ('abc' ))
495+
496+ def test_sequence_inplace_repeat_errors (self ):
497+ # Corner case, custom subtype returns `NotImplemented`:
498+ class CustomListWithNI (list ):
499+ def __mul__ (self , other ):
500+ return NotImplemented
501+ def __imul__ (self , other ):
502+ return NotImplemented
503+
504+ sequence = CustomListWithNI (['a' , 'b' ])
505+ with self .assertRaises (TypeError ):
506+ _testcapi .sequence_inplace_repeat (sequence , 3 )
507+ self .assertEqual (sequence , CustomListWithNI (['a' , 'b' ])) # not changed
508+
509+ # Non-sequences do not work (even with `__mul__` and `__imul__`):
510+ class CustomTypeWithMul :
511+ def __mul__ (self , other ):
512+ return 0
513+ def __imul__ (self , other ):
514+ return 1 # won't be called
515+
516+ not_sequence = CustomTypeWithMul ()
517+ with self .assertRaises (TypeError ):
518+ _testcapi .sequence_inplace_repeat (not_sequence , 1 )
519+
407520 def test_trashcan_subclass (self ):
408521 # bpo-35983: Check that the trashcan mechanism for "list" is NOT
409522 # activated when its tp_dealloc is being called by a subclass
0 commit comments