@@ -560,6 +560,221 @@ def __getitem__(self, other): return 5 # so that C is a sequence
560560 self .assertEqual (operator .ixor (c , 5 ), "ixor" )
561561 self .assertEqual (operator .iconcat (c , c ), "iadd" )
562562
563+ def test_concat_subclasses (self ):
564+ operator = self .module
565+
566+ # Simple cases:
567+ data1 = [1 , 2 ]
568+ data2 = ['a' , 'b' ]
569+ self .assertEqual (operator .concat (data1 , data2 ), [1 , 2 , 'a' , 'b' ])
570+ self .assertEqual (operator .concat (data1 , data2 ), data1 + data2 )
571+ self .assertEqual (data1 , [1 , 2 ]) # must not change
572+ self .assertEqual (data2 , ['a' , 'b' ]) # must not change
573+
574+ data1 = (1 , 2 )
575+ data2 = ('a' , 'b' )
576+ self .assertEqual (operator .concat (data1 , data2 ), (1 , 2 , 'a' , 'b' ))
577+ self .assertEqual (operator .concat (data1 , data2 ), data1 + data2 )
578+ self .assertEqual (data1 , (1 , 2 )) # must not change
579+ self .assertEqual (data2 , ('a' , 'b' )) # must not change
580+
581+ data1 = '12'
582+ data2 = 'ab'
583+ self .assertEqual (operator .concat (data1 , data2 ), '12ab' )
584+ self .assertEqual (operator .concat (data1 , data2 ), data1 + data2 )
585+ self .assertEqual (data1 , '12' ) # must not change
586+ self .assertEqual (data2 , 'ab' ) # must not change
587+
588+ # Subclasses:
589+ class ListSubclass (list ):
590+ pass
591+
592+ data1 = ListSubclass ([1 , 2 ])
593+ data2 = ListSubclass (['a' , 'b' ])
594+ res = operator .concat (data1 , data2 )
595+ self .assertIs (type (res ), list )
596+ self .assertEqual (res , [1 , 2 , 'a' , 'b' ])
597+ self .assertEqual (data1 , ListSubclass ([1 , 2 ])) # must not change
598+ self .assertEqual (data2 , ListSubclass (['a' , 'b' ])) # must not change
599+
600+ data1 = ListSubclass ([1 , 2 ])
601+ data2 = ['a' , 'b' ]
602+ res = operator .concat (data1 , data2 )
603+ self .assertIs (type (res ), list )
604+ self .assertEqual (res , [1 , 2 , 'a' , 'b' ])
605+ self .assertEqual (data1 , ListSubclass ([1 , 2 ])) # must not change
606+ self .assertEqual (data2 , ['a' , 'b' ]) # must not change
607+
608+ data1 = [1 , 2 ]
609+ data2 = ListSubclass (['a' , 'b' ])
610+ res = operator .concat (data1 , data2 )
611+ self .assertIs (type (res ), list )
612+ self .assertEqual (res , [1 , 2 , 'a' , 'b' ])
613+ self .assertEqual (data1 , [1 , 2 ]) # must not change
614+ self .assertEqual (data2 , ListSubclass (['a' , 'b' ])) # must not change
615+
616+ # Custom type with `__add__`:
617+ class TupleSubclass (tuple ):
618+ def __add__ (self , other ):
619+ return TupleSubclass (list (other ) + list (self ))
620+
621+ data1 = TupleSubclass ([1 , 2 ])
622+ data2 = TupleSubclass (['a' , 'b' ])
623+ res = operator .concat (data1 , data2 )
624+ self .assertIsInstance (res , TupleSubclass )
625+ self .assertEqual (res , TupleSubclass (['a' , 'b' , 1 , 2 ]))
626+ self .assertEqual (data1 , TupleSubclass ([1 , 2 ])) # must not change
627+ self .assertEqual (data2 , TupleSubclass (['a' , 'b' ])) # must not change
628+
629+ data1 = TupleSubclass ([1 , 2 ])
630+ data2 = ('a' , 'b' )
631+ res = operator .concat (data1 , data2 )
632+ self .assertIsInstance (res , TupleSubclass )
633+ self .assertEqual (res , TupleSubclass (['a' , 'b' , 1 , 2 ]))
634+ self .assertEqual (data1 , TupleSubclass ([1 , 2 ])) # must not change
635+ self .assertEqual (data2 , ('a' , 'b' )) # must not change
636+
637+ data1 = (1 , 2 )
638+ data2 = TupleSubclass (['a' , 'b' ])
639+ res = operator .concat (data1 , data2 )
640+ self .assertIs (type (res ), tuple )
641+ self .assertEqual (res , (1 , 2 , 'a' , 'b' ))
642+ self .assertEqual (data1 , (1 , 2 )) # must not change
643+ self .assertEqual (data2 , TupleSubclass (['a' , 'b' ])) # must not change
644+
645+ # Corner cases:
646+ self .assertEqual (operator .concat ([1 , 2 ], []), [1 , 2 ])
647+ self .assertEqual (operator .concat ([], [1 , 2 ]), [1 , 2 ])
648+ self .assertEqual (operator .concat ([], []), [])
649+
650+ # Type errors:
651+ self .assertRaises (TypeError , operator .concat , [], 1 )
652+ self .assertRaises (TypeError , operator .concat , 1 , [])
653+ self .assertRaises (TypeError , operator .concat , 1 , 1 )
654+ self .assertRaises (TypeError , operator .concat , (), [])
655+ self .assertRaises (TypeError , operator .concat , [], ())
656+
657+ # Returns `NotImplemented`:
658+ class BrokenSeq (tuple ):
659+ def __add__ (self , other ):
660+ return NotImplemented
661+
662+ self .assertRaises (TypeError , operator .concat , BrokenSeq (), BrokenSeq ())
663+
664+ def test_iconcat (self ):
665+ operator = self .module
666+
667+ # Simple cases:
668+ data1 = [1 , 2 , 3 ]
669+ data2 = ['a' , 'b' ]
670+ res = operator .iconcat (data1 , data2 )
671+ self .assertEqual (res , [1 , 2 , 3 , 'a' , 'b' ])
672+ self .assertEqual (data1 , [1 , 2 , 3 , 'a' , 'b' ]) # must change
673+ self .assertEqual (data2 , ['a' , 'b' ]) # must not change
674+
675+ data1 = (1 , 2 )
676+ data2 = ('a' , 'b' )
677+ self .assertEqual (operator .iconcat (data1 , data2 ), (1 , 2 , 'a' , 'b' ))
678+ self .assertEqual (operator .iconcat (data1 , data2 ), data1 + data2 )
679+ self .assertEqual (data1 , (1 , 2 )) # must not change
680+ self .assertEqual (data2 , ('a' , 'b' )) # must not change
681+
682+ data1 = '12'
683+ data2 = 'ab'
684+ self .assertEqual (operator .iconcat (data1 , data2 ), '12ab' )
685+ self .assertEqual (operator .iconcat (data1 , data2 ), data1 + data2 )
686+ self .assertEqual (data1 , '12' ) # must not change
687+ self .assertEqual (data2 , 'ab' ) # must not change
688+
689+ # Subclasses:
690+ class ListSubclass (list ):
691+ pass
692+
693+ data1 = ListSubclass ([1 , 2 ])
694+ data2 = ListSubclass (['a' , 'b' ])
695+ res = operator .iconcat (data1 , data2 )
696+ self .assertIsInstance (res , ListSubclass )
697+ self .assertEqual (res , ListSubclass ([1 , 2 , 'a' , 'b' ]))
698+ self .assertIsInstance (data1 , ListSubclass )
699+ self .assertEqual (data1 , ListSubclass ([1 , 2 , 'a' , 'b' ])) # must change
700+ self .assertEqual (data2 , ListSubclass (['a' , 'b' ])) # must not change
701+
702+ data1 = ListSubclass ([1 , 2 ])
703+ data2 = ['a' , 'b' ]
704+ res = operator .iconcat (data1 , data2 )
705+ self .assertIsInstance (res , ListSubclass )
706+ self .assertEqual (res , ListSubclass ([1 , 2 , 'a' , 'b' ]))
707+ self .assertIsInstance (data1 , ListSubclass )
708+ self .assertEqual (data1 , ListSubclass ([1 , 2 , 'a' , 'b' ])) # must change
709+ self .assertEqual (data2 , ['a' , 'b' ]) # must not change
710+
711+ data1 = [1 , 2 ]
712+ data2 = ListSubclass (['a' , 'b' ])
713+ res = operator .iconcat (data1 , data2 )
714+ self .assertIs (type (res ), list )
715+ self .assertEqual (res , [1 , 2 , 'a' , 'b' ])
716+ self .assertIs (type (data1 ), list )
717+ self .assertEqual (data1 , [1 , 2 , 'a' , 'b' ]) # must change
718+ self .assertEqual (data2 , ListSubclass (['a' , 'b' ])) # must not change
719+
720+ # Custom type with `__add__`:
721+ class TupleSubclass (tuple ):
722+ def __add__ (self , other ):
723+ return TupleSubclass (list (other ) + list (self ))
724+
725+ data1 = TupleSubclass ([1 , 2 ])
726+ data2 = TupleSubclass (['a' , 'b' ])
727+ res = operator .iconcat (data1 , data2 )
728+ self .assertIsInstance (res , TupleSubclass )
729+ self .assertEqual (res , TupleSubclass (['a' , 'b' , 1 , 2 ]))
730+ self .assertEqual (data1 , TupleSubclass ([1 , 2 ])) # must not change
731+ self .assertEqual (data2 , TupleSubclass (['a' , 'b' ])) # must not change
732+
733+ data1 = TupleSubclass ([1 , 2 ])
734+ data2 = ('a' , 'b' )
735+ res = operator .iconcat (data1 , data2 )
736+ self .assertIsInstance (res , TupleSubclass )
737+ self .assertEqual (res , TupleSubclass (['a' , 'b' , 1 , 2 ]))
738+ self .assertEqual (data1 , TupleSubclass ([1 , 2 ])) # must not change
739+ self .assertEqual (data2 , ('a' , 'b' )) # must not change
740+
741+ data1 = (1 , 2 )
742+ data2 = TupleSubclass (['a' , 'b' ])
743+ res = operator .iconcat (data1 , data2 )
744+ self .assertIs (type (res ), tuple )
745+ self .assertEqual (res , TupleSubclass ([1 , 2 , 'a' , 'b' ]))
746+ self .assertEqual (data1 , (1 , 2 )) # must not change
747+ self .assertEqual (data2 , TupleSubclass (['a' , 'b' ])) # must not change
748+
749+ # Corner cases:
750+ self .assertEqual (operator .iconcat ([1 , 2 ], []), [1 , 2 ])
751+ self .assertEqual (operator .iconcat ([], [1 , 2 ]), [1 , 2 ])
752+ self .assertEqual (operator .iconcat ([], []), [])
753+ self .assertEqual (operator .iconcat ([], ()), [])
754+
755+ # Type errors:
756+ self .assertRaises (TypeError , operator .iconcat , [], 1 )
757+ self .assertRaises (TypeError , operator .iconcat , 1 , [])
758+ self .assertRaises (TypeError , operator .iconcat , 1 , 1 )
759+ self .assertRaises (TypeError , operator .iconcat , (), [])
760+
761+ # Returns `NotImplemented`:
762+ class BrokenSeq1 (list ):
763+ def __add__ (self , other ):
764+ return NotImplemented
765+ def __iadd__ (self , other ):
766+ return NotImplemented
767+
768+ self .assertRaises (TypeError ,
769+ operator .iconcat , BrokenSeq1 (), BrokenSeq1 ())
770+
771+ class BrokenSeq2 (tuple ):
772+ def __add__ (self , other ):
773+ return NotImplemented
774+
775+ self .assertRaises (TypeError ,
776+ operator .iconcat , BrokenSeq2 (), BrokenSeq2 ())
777+
563778 def test_iconcat_without_getitem (self ):
564779 operator = self .module
565780
0 commit comments