Search code examples
pythonabc

Python : subclass `type` to create specialized types (e.g. a "list of int")


I am trying to subclass type in order to create a class allowing to build specialized types. e.g. a ListType :

>>> ListOfInt = ListType(list, value_type=int)
>>> issubclass(ListOfInt, list)
True
>>> issubclass(list, ListOfInt)
False
>>> # And so on ...

However, this ListOfInt will never be used to create instances ! I just use it as an instance of type that I can manipulate to compare with other types ... In particular, in my case I need to look-up for a suitable operation, according to the type of input, and I need the type to contain more precisions (like list of int or XML string, etc ...).

So here's what I came up with :

class SpzType(type):

    __metaclass__ = abc.ABCMeta

    @classmethod
    def __subclasshook__(cls, C):
        return NotImplemented

    def __new__(cls, base, **features):
        name = 'SpzOf%s' % base.__name__
        bases = (base,)
        attrs = {}
        return super(SpzType, cls).__new__(cls, name, bases, attrs)

    def __init__(self, base, **features):
        for name, value in features.items():
            setattr(self, name, value)

The use of abc is not obvious in the code above ... however if I want to write a subclass ListType like in the example on top, then it becomes useful ...

The basic functionality actually works :

>>> class SimpleType(SpzType): pass
>>> t = SimpleType(int)
>>> issubclass(t, int)
True
>>> issubclass(int, t)
False

But when I try to check if t is an instance of SpzType, Python freaks out :

>>> isinstance(t, SpzType)
TypeError: __subclasscheck__() takes exactly one argument (0 given)

I explored with pdb.pm() what was going on, and I found out that the following code raises the error :

>>> SpzType.__subclasscheck__(SimpleType)
TypeError: __subclasscheck__() takes exactly one argument (0 given)

WeIrD ?! Obviously there is an argument ... So what does that mean ? Any idea ? Did I misuse abc ?


Solution

  • Thanks to comment from kindall, I have refactored the code to the following :

    class SpzType(abc.ABCMeta):
    
        def __subclasshook__(self, C):
            return NotImplemented
    
        def __new__(cls, base, **features):
            name = 'SpzOf%s' % base.__name__
            bases = (base,)
            attrs = {}
            new_spz = super(SpzType, cls).__new__(cls, name, bases, attrs)
            new_spz.__subclasshook__ = classmethod(cls.__subclasshook__)
            return new_spz
    
        def __init__(self, base, **features):
            for name, value in features.items():
                setattr(self, name, value)
    

    So basically, SpzType is now a subclass of abc.ABCMeta, and subclasshook is implemented as an instance method. It works great and it is (IMO) elegant !!!

    EDIT : There was a tricky thing ... because __subclasshook__ needs to be a classmethod, so I have to call the classmethod function manually... otherwise it doesn't work if I want to implement __subclasshook__.