I was also going to undertake some research into the methods that
TypedList
needed to override or otherwise implement in order to make
it work the way it needs to. That research leads me to believe that there are a
total of 8 methods that I need to be concerned with:
__init__
: the object constructor has to check any items supplied to it for type-restrictions, but if there are no invalid types provided, it can calllist.__init__
and return whatever comes back from that;__add__
: called when concatenating two lists (in this case). I have to assume that any items provided are suspect until proven otherwise. I also couldn't determine how many times this method gets called in cases of multiple additions;__iadd__
: called when concatenating with+=
, same assumptions as for__add__
;__setitem__
: called when assigning a value to a specific position within the list. The value is treated as a non-iterable (e.g., passing a list results in a list in the position of the original list);__setslice__
: Called when assigning values to a slice, items being set need to be type-checked;append
: Called when appending values to the list, items added need to be type-checked;insert
: Called when inserting a value to a specific position in the list, the value needs to be type-checked;extend
: Called to extend items to the end of the list, same drill;
class MyList( list, object ): def __init__( self, iterable=None ): print "MyList.__init__( %s )" % ( iterable ) list.__init__( self, iterable ) def __add__( self, items ): print "MyList.__add__( %s )" % ( items ) return list.__add__( self, items ) def __iadd__( self, items ): print "MyList.__iadd__( %s )" % ( items ) return list.__iadd__( self, items ) def __setitem__( self, index, item ): print "MyList.__setitem__( %s, %s )" % ( index, item ) return list.__setitem__( self, index, item ) def __setslice__( self, start, end, items ): print "MyList.__setslice__( %s, %s, %s )" % ( start, end, items ) return list.__setslice__( self, start, end, items ) def append( self, item ): print "MyList.append( %s )" % ( item ) return list.append( self, item ) def insert( self, index, item ): print "MyList.insert( %s, %s )" % ( index, item ) return list.insert( self, index, item ) def extend( self, items ): print "MyList.extend( %s )" % ( items ) return list.extend( self, items ) tester = MyList( ['eek'] ) # uses __init__ tester += [ 'ook' ] # uses __iadd__ tester.append( 'eek' ) # uses append tester.insert( 1, 'OOOOK!' ) # uses insert tester[1:2] = [ 'spam', 'eggs' ] # uses __setslice__ tester += [ 'oooo', 'ooooo!' ] # uses __iadd__ tester[2] = 'bork!' # uses __setitem__ tester.extend( [ 'eeee', 'eeee' ] ) # uses extend tester = tester + [ 'mooo!' ] + [ 'me' ] # uses __add__ print print tester
This little script provided a lot of insight, actually - Though I'd seen some
of the documentation for __add__
and __iadd__
, for
example, it never really stuck in my head that these were the methods that were
called when +
and +=
operators were used.
So, with our unit-tests set, and a solid feel for what the code needed to
override from list
, TypedList
was pretty easily
written:
TypedList (Nominal final class):
class TypedList( BaseTypedCollection, list ): """Represents a strongly-typed List.""" ################################## # Class Attributes # ################################## ################################## # Class Property-Getter Methods # ################################## ################################## # Class Property-Setter Methods # ################################## ################################## # Class Property-Deleter Methods # ################################## ################################## # Class Properties # ################################## ################################## # Object Constructor # ################################## @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) @DocumentArgument( 'argument', 'memberTypes', None, '(Iterable, required) An iterable collection of item-types to be allowed as members in the list.' ) @DocumentArgument( 'argument', 'iterable', None, '(Iterable, required) An iterable collection of item-values to populate the TypedList with at creation.' ) def __init__( self, memberTypes, iterable=None ): """Object constructor.""" # Nominally final: Don't allow any class other than this one if self.__class__ != TypedList: raise NotImplementedError( 'TypedList is (nominally) a final class, and is not intended to be derived from.' ) self._SetMemberTypes( memberTypes ) list.__init__( self ) if iterable != None: for item in iterable: self.append( item ) ################################## # Object Destructor # ################################## ################################## # Class Methods # ################################## ################################## # Methods overridden from list # ################################## @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) def __add__( self, items ): """Called when adding items to the list with a "+" (e.g., TL + items).""" if not isinstance( items, list ): raise TypeError( 'TypedList.__add__ expects a list (or list-derived) instance of values.' % ( self._memberTypes ) ) for item in items: if not self.IsValidMemberType( item ): raise TypeError( 'TypedList.__add__ expects a list (or list-derived) instance of values of any of %s. %s is not valid.' % ( self._memberTypes, items ) ) return list.__add__( self, items ) @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) def __iadd__( self, items ): """Called when adding items to the list with a "+=" (e.g., TL += item).""" if not isinstance( items, list ): raise TypeError( 'TypedList.__iadd__ expects a list (or list-derived) instance of values.' % ( self._memberTypes ) ) for item in items: if not self.IsValidMemberType( item ): raise TypeError( 'TypedList.__iadd__ expects a list (or list-derived) instance of values of any of %s. %s is not valid.' % ( self._memberTypes, items ) ) return list.__iadd__( self, items ) @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) def __setitem__( self, index, item ): """Called when setting the value of a specified location in the list (TL[n] = item).""" if not self.IsValidMemberType( item ): raise TypeError( 'TypedList.__setitem__ expects an instance of any of %s.' % ( self._memberTypes ) ) return list.__setitem__( self, index, item ) @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) def __setslice__( self, start, end, items ): """Called when setting the value(s) of a slice-location in the list (TL[1:2] = item).""" for item in items: if not self.IsValidMemberType( item ): raise TypeError( 'TypedList.__setslice__ expects an iterable of instances of any of %s.' % ( self._memberTypes ) ) return list.__setslice__( self, start, end, items ) @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) def append( self, item ): """Called when appending items to the list.""" if not self.IsValidMemberType( item ): raise TypeError( 'TypedList.append expects an instance of any of %s.' % ( self._memberTypes ) ) return list.append( self, item ) @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) def extend( self, items ): """Called when extending items to a the end of the list.""" if not isinstance( items, list ): raise TypeError( 'TypedList.extend expects a list (or list-derived) instance of values.' % ( self._memberTypes ) ) for item in items: if not self.IsValidMemberType( item ): raise TypeError( 'TypedList.extend expects a list (or list-derived) instance of values of any of %s. %s is not valid.' % ( self._memberTypes, items ) ) return list.extend( self, items ) @DocumentArgument( 'argument', 'self', None, SelfDocumentationString ) def insert( self, index, item ): """Called when inserting items to a specified location in the list (TL[1] = item).""" if not self.IsValidMemberType( item ): raise TypeError( 'TypedList.insert expects an instance of any of %s.' % ( self._memberTypes ) ) return list.insert( self, index, item ) __all__ += [ 'TypedList' ]
Ultimately, as with TypedDictionary
earlier, there's not a huge
amount of code to be written. Python's built-ins and the methods specific to
adding items to or replacing items in a list were the only methods that had to
be overridden, and there were only two patterns for those modifications:
- If the method expected a list-type (e.g.,
__add__
,__iadd__
,__setslice__
andextend
):- The supplied items are checked to make sure that they are
derived from a list. This allows list-types and extensions
of list types (like
Typedlist
) to be used. - Once the basic supplied type is successfully checked, the
items in it have to be individually type-checked
(using
IsValidMemberType
), and if any of them are invalid, raise an error. - If everything is good, then call (and return) the parent list-class method with the supplied value(s).
- The supplied items are checked to make sure that they are
derived from a list. This allows list-types and extensions
of list types (like
- If the method expects a non-list instance value, then all that need
be done is type-check the value (again, with
IsValidMemberType
):- If it's invalid, raise a
TypeError
. - Otherwise, call (and return) the parent list-class method with the supplied value.
- If it's invalid, raise a
- Line(s)
- 34-35
- Treat
TypedList
as a final class, and don't allow extension. - 36
- Set the member-types allowed by the instance.
- 37
- Makes sure that the instance calls a basic list-constructor. We can (and
should) ignore any supplied instance-values in the
iterable
argument when calling the parent constructor, though, because we want to use the overriddenappend
method to build out the instance's list of values. - 38-40
- Checks to see if the constructor is being passed an iterable collection of values to populate the instance with, loops over them if they are supplied, and appends them to the instance's values one by one.
- I could've just as easily looped over any supplied
iterable
values in the constructor, raising an error at the first bad type supplied, and then called the parent constructor with theiterable
value. I have a gut-level feeling, however, that taking that approach could raise issues in the future, and since I'd've had to iterate over the values anyway, it just seemed safer to implement the way I did. I can't justify it, but I'm going to trust my instincts. - 55-62, 65-72, 82-87, 97-104
- Uses the list-checking pattern noted above to override
__add__
,__iadd__
,__setslice__
andextend
, respectively. - 75-81, 90-94, 107-111
- Uses the value-checking pattern noted above to override
__setitem__
,append
andinsert
, respectively.
No comments:
Post a Comment