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
2
>>> l[1] = 1.2 7
>>> l
[1, 1.2]
>>> l.color = 'red' 8



1

A regular class statement.

2

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

3

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

4

Append a float and...

5

watch it automatically become an integer.

6

Otherwise, it behaves like any other list.

7

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.

7

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


1

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

2

Setting any attribute on this raises an exception.

3

__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.

4

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

5

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

1

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

2

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

3

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).