Subclassing Built-in Types

Subclassing built-in types is straightforward. Actually we have been doing it all along (whenever we subclass <type 'object'>). Some built-in types (types.FunctionType, for example) are not subclassable (not yet, at least). However, here we talk about subclassing <type 'list'>, <type 'tuple'> and other basic data types.

Example 3.3. Subclassing <type 'list'>

>>> class MyList(list): 1
...     "A list that converts appended items to ints"
...     def append(self, item): 2
...         list.append(self, int(item)) 3
>>> l = MyList() 
>>> l.append(1.3) 4
>>> l.append(444) 
>>> l
[1, 444] 5
>>> len(l) 6
>>> l[1] = 1.2 7
>>> l
[1, 1.2]
>>> l.color = 'red' 8


A regular class statement.


Define the method to be overridden. In this case we will convert all items passed through append() to integers.


Upcall to the base if required. list.append() works like an unbound method, and is passed the instance as the first argument.


Append a float and...


watch it automatically become an integer.


Otherwise, it behaves like any other list.


This doesn't go through append. We would have to define __setitem__() in our class to massage such data. The upcall would be to list.__setitem__(self,item). Note that the special methods such as __setitem__ exist on built-in types.


We can set attributes on our instance. This is because it has a __dict__.

Basic lists do not have __dict__ (and so no user-defined attributes), but ours does. This is usually not a problem and may even be what we want. If we use a very large number of MyLists, however, we could optimize our program by telling Python not to create the __dict__ for instances of MyList.

Example 3.4. Using __slots__ for optimization

class MyList(list):
    "A list subclass disallowing any user-defined attributes"
    __slots__ = [] 1

ml = MyList()
ml.color = 'red' # raises exception! 2

class MyListWithFewAttrs(list):
    "A list subclass allowing specific user-defined attributes"

    __slots__ = ['color'] 3

mla = MyListWithFewAttrs()
mla.color = 'red' 4
mla.weight = 50 # raises exception! 5


The __slots__ class attribute tells Python to not create a __dict__ for instances of this type.


Setting any attribute on this raises an exception.


__slots__ can contain a list of strings. The instances still don't get a real dictionary for __dict__, but they get a proxy. Python reserves space in the instance for the specified attributes.


Now, if an attribute has space reserved, it can be used.


Otherwise, it cannot. This will raise an exception.

The purpose and recommended use of __slots__ is for optimization. After a type is defined, its slots cannot be changed. Also, every subclass must define __slots__, otherwise its instances will end up having __dict__.

We can create a list even by instantiating it like any other type: list([1,2,3]). This means list.__init__() accepts the same argument (i.e. any iterable) and initializes a list. We can customize initialization in a subclass by redefining __init__() and upcalling __init__() on the base.

Tuples are immutable and different from lists. Once an instance is created, it cannot be changed. Note that the instance of a type already exists when __init__() is called (in fact the instance is passed as the first argument). The __new__() static method of a type is called to create an instance of the type. It is passed the type itself as the first argument, and passed through other initial arguments (similar to __init__()). We use this to customize immutable types like a tuple.

Example 3.5. Customizing creation of subclasses

class MyList(list):

    def __init__(self, itr): 1
        list.__init__(self, [int(x) for x in itr])

class MyTuple(tuple):
    def __new__(typ, itr): 2
        seq = [int(x) for x in itr]
        return tuple.__new__(typ, seq) 3


For a list, we massage the arguments and hand them over to list.__init__().


For a tuple, we have to override __new__().


A __new__() should always return. It is supposed to return an instance of the type.

The __new__() method is not special to immutable types, it is used for all types. It is also converted to a static method automatically by Python (by virtue of its name).