@@ -27,6 +27,12 @@ def __init__(self, allowed_contents, parent=None):
2727 self .parent = parent
2828
2929 def _handle_append (self , obj ):
30+ self ._check_allowed (obj )
31+ if self ._contains (obj ):
32+ raise ValueError ("Cannot add this object because it is already contained within the list" )
33+ self ._set_parent (obj )
34+
35+ def _check_allowed (self , obj ):
3036 if not (
3137 isinstance (obj , self .allowed_contents )
3238 or ( # also allow proxy objects of the correct type
@@ -35,9 +41,7 @@ def _handle_append(self, obj):
3541 ):
3642 raise TypeError (f"Object is a { type (obj )} . It should be one of { self .allowed_contents } ." )
3743
38- if self ._contains (obj ):
39- raise ValueError ("Cannot add this object because it is already contained within the list" )
40-
44+ def _set_parent (self , obj ):
4145 # set the child-parent relationship
4246 if self .parent :
4347 relationship_name = self .parent .__class__ .__name__ .lower ()
@@ -49,11 +53,8 @@ def _handle_append(self, obj):
4953 setattr (obj , relationship_name , self .parent )
5054
5155 def _contains (self , obj ):
52- if self ._items is None :
53- obj_ids = []
54- else :
55- obj_ids = [id (item ) for item in self ._items ]
56- return id (obj ) in obj_ids
56+ # membership is by identity
57+ return self ._items is not None and any (item is obj for item in self ._items )
5758
5859 def __str__ (self ):
5960 return str (self ._items )
@@ -75,12 +76,10 @@ def __radd__(self, objects):
7576 return objects + self ._items
7677
7778 def __contains__ (self , key ):
78- return key in self ._items
79+ return self ._contains ( key )
7980
8081 def __iadd__ (self , objects ):
81- for obj in objects :
82- self ._handle_append (obj )
83- self ._items .extend (objects )
82+ self .extend (objects )
8483 return self
8584
8685 def __iter__ (self ):
@@ -93,25 +92,55 @@ def __len__(self):
9392 return len (self ._items )
9493
9594 def __setitem__ (self , key , value ):
96- self ._items [key ] = value
95+ if isinstance (key , slice ):
96+ values = list (value )
97+ for obj in values :
98+ self ._check_allowed (obj )
99+ # assign to a copy first, so that nothing changes if the assignment or the checks fail
100+ new_items = self ._items [:]
101+ new_items [key ] = values
102+ if len ({id (obj ) for obj in new_items }) != len (new_items ):
103+ raise ValueError ("Cannot add this object because it is already contained within the list" )
104+ for obj in values :
105+ self ._set_parent (obj )
106+ self ._items = new_items
107+ else :
108+ if value is self ._items [key ]:
109+ return
110+ self ._handle_append (value )
111+ self ._items [key ] = value
97112
98113 def append (self , obj ):
99114 self ._handle_append (obj )
100115 self ._items .append (obj )
101116
102117 def extend (self , objects ):
118+ # objects may be an iterator that can only be consumed once (e.g., a generator), and we need to iterate twice
119+ objects = list (objects )
120+ # check everything before changing anything, so that a failure leaves the list unchanged.
121+ # Building the set of ids once, rather than calling _contains() for each object, keeps this O(n + m)
122+ seen = {id (item ) for item in self ._items or ()}
123+ for obj in objects :
124+ self ._check_allowed (obj )
125+ if id (obj ) in seen :
126+ raise ValueError ("Cannot add this object because it is already contained within the list" )
127+ seen .add (id (obj ))
103128 for obj in objects :
104- self ._handle_append (obj )
129+ self ._set_parent (obj )
105130 self ._items .extend (objects )
106131
107132 def clear (self ):
108133 self ._items = []
109134
110135 def count (self , value ):
111- return self ._items . count (value )
136+ return int ( self ._contains (value ) )
112137
113138 def index (self , value , start = 0 , stop = sys .maxsize ):
114- return self ._items .index (value , start , stop )
139+ items = self ._items or []
140+ for i in range (* slice (start , stop ).indices (len (items ))):
141+ if items [i ] is value :
142+ return i
143+ raise ValueError ("object is not in list" )
115144
116145 def insert (self , index , obj ):
117146 self ._handle_append (obj )
@@ -121,10 +150,10 @@ def pop(self, index=-1):
121150 return self ._items .pop (index )
122151
123152 def remove (self , value ):
124- return self ._items . remove (value )
153+ del self ._items [ self . index (value )]
125154
126155 def reverse (self ):
127- raise self ._items .reverse ()
156+ self ._items .reverse ()
128157
129158 def sort (self , * args , key = None , reverse = False ):
130159 self ._items .sort (* args , key = key , reverse = reverse )
0 commit comments