Python:子类化`type`以创建专用类型(例如“整数列表”)

7 投票
4 回答
4266 浏览
提问于 2025-04-16 19:28

我正在尝试创建一个新的类,这个类可以用来构建一些特别的类型,比如说一个叫做 ListType 的类型:

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

不过,这个 ListOfInt 其实是不会被用来创建实例的!我只是把它当作 type 的一个实例来用,方便我跟其他类型进行比较……具体来说,我需要根据输入的类型来寻找合适的操作,而这个类型需要包含更多的信息(比如 list of int 或者 XML string 等等)。

所以我想出了这个:

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)

在上面的代码中,abc 的用法并不是很明显……不过如果我想像上面例子那样写一个子类 ListType,那它就变得有用了……

基本功能其实是可以正常工作的:

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

但是当我尝试检查 t 是否是 SpzType 的实例时,Python 就出问题了:

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

我用 pdb.pm() 调试了一下,发现以下代码会引发错误:

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

这真奇怪?!显然有个参数……那这是什么意思呢?有什么想法吗?我是不是用错了 abc

4 个回答

1

我不太明白你想要达到什么效果。也许直接使用 abc 不如用 collections 模块更好?

关于通用集合类的更多信息,可以查看 PEP 3119

1

你想做的事情可能可以通过一个类工厂函数来更简单地实现,像下面这样。对我来说,这样可以更清楚地理清我想要操作的不同层级。

def listOf(base, types={}, **features):
    key = (base,) + tuple(features.items())
    if key in types:
        return types[key]
    else:

        if not isinstance(base, type):
            raise TypeError("require element type, got '%s'" % base)

        class C(list):

             def __init__(self, iterable=[]):
                 for item in iterable:
                     try:    # try to convert to desired type
                         self.append(self._base(item))
                     except ValueError:
                         raise TypeError("value '%s' not convertible to %s"
                            % (item, self._base.__name__))

              # similar methods to type-check other list mutations

        C.__name__ = "listOf(%s)" % base.__name__
        C._base = base
        C.__dict__.update(features)  
        types[key] = C
        return C

注意,我在这里使用了一个 dict 作为缓存,这样对于特定的元素类型和特性组合,你会得到相同的类对象。这就意味着 listOf(int) is listOf(int) 总是会返回 True

1

感谢kindall的评论,我把代码改成了下面这样:

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)

简单来说,SpzType现在是abc.ABCMeta的一个子类,而subclasshook被实现成了一个实例方法。这样做效果很好,我觉得这很优雅!!!

补充一下:有个小问题……因为__subclasshook__需要是一个类方法,所以我必须手动调用这个类方法……否则如果我想实现__subclasshook__的话,它就不工作了。

撰写回答