【问题标题】:Python : subclass `type` to create specialized types (e.g. a "list of int")Python:子类`type`来创建专门的类型(例如“int列表”)
【发布时间】:2011-09-14 00:15:52
【问题描述】:

我正在尝试继承 type 以创建一个允许构建专用类型的类。例如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 吗?

【问题讨论】:

  • 您确定不希望 ABCMeta 作为构造类型的元类,而不是您的元类吗?
  • 如果您想要一种能够理解参数化类型并且可以进行编译时类型检查的语言,那么请使用一种。假设你得到了这个工作,我看不出它怎么可能比仅仅子类化列表和拒绝界面中的非ints 获得任何东西。
  • @kindall: 嗯......是的,你可能是对的!因为在我的情况下,实例实际上是类型本身,我可能对此感到困惑。想一想,如果我希望我的 ListOfInt 被检测为 ListOfObject 的子类,则 issubclasshook 应该在 ListOfObject 类上。明天我会试试,如果可行,请发布答案!
  • @Karl Knechtel:显然,如果我正在做这样复杂的事情,那是因为我需要!它实际上是针对我正在编写的一个库 (pypi.python.org/pypi/any2any/0.1),而 SpzType 的那些实例并不意味着要实例化自己。我只是对定义语义ListOfObject、JSONStr 等感兴趣...
  • @kindall:即使您可能是对的 - 并且发布的代码无法实现我想要的 - (我还没有尝试过),肯定有一些时髦的事情发生......甚至可能是一个错误,不是吗?

标签: python abc


【解决方案1】:

我不太确定您想要实现什么。也许使用collections模块而不是直接使用abc更好?

PEP 3119中有更多关于泛型集合类的信息

【讨论】:

  • 嗯......列表只是一个例子......我想要做的是有类型的实例,包含额外的信息。想象一下XMLStr 或JSONStr 类型(SpzType 允许轻松构建,并且尊重issubclass(XMLStr, str) == True)!?想象一个类型OddInt,……等等……
【解决方案2】:

使用如下的类工厂函数可能更容易完成您想做的事情。至少对我来说,这让我可以更直接地保持我正在尝试操作的各个级别。

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。

【讨论】:

  • 我以前也尝试过这样工作,但结果不佳,重复次数多(例如,ListSpzType 的工厂函数,StrSpzType 的工厂函数,@987654327 @, ...) 并且语法对最终用户来说不是很好。
  • 是的,我想你真正需要的是一个工厂函数,它可以为任意基类型制作这样的工厂函数。这可能会和你想要做的一样混乱。
  • 实际上,我收回了这一点。您可以很容易地使用 type() 构造函数从传入的类创建新的子类。然后为它制作一个类装饰器......
【解决方案3】:

感谢 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 被实现为实例方法。它工作得很好,而且(IMO)很优雅!!!

编辑:有一个棘手的事情......因为__subclasshook__需要是一个类方法,所以我必须手动调用类方法函数......否则如果我想实现__subclasshook__它不起作用。

【讨论】:

  • 是的,这很漂亮!我刚刚使用装饰器编写了一个解决方案,如果您愿意,我会发布,但我认为您已经掌握了节奏。
  • 我可以看看它,因为我不知道你会如何使用装饰器!
  • 发布装饰器版本。
【解决方案4】:

这是我的其他答案的装饰器版本,适用于任何类。装饰器返回一个工厂函数,该函数返回具有所需属性的原始类的子类。这种方法的好处是它不需要元类,因此如果需要,您可以使用元类(例如ABCMeta)而不会发生冲突。

还要注意,如果基类使用元类,则该元类将用于实例化生成的子类。如果您愿意,您可以硬编码所需的元类,或者,您知道,编写一个装饰器,将元类变成模板类的装饰器……它一直是装饰器!

如果存在,则将类方法__classinit__() 传递给工厂的参数,因此类本身可以有代码来验证参数并设置其属性。 (这将在元类的__init__() 之后调用。)如果__classinit__() 返回一个类,该类将由工厂返回代替生成的类,因此您甚至可以通过这种方式扩展生成过程(例如,对于一个类型-checked 列表类,您可以返回两个内部类之一,具体取决于项目是否应强制转换为元素类型。

如果__classinit__() 不存在,则传递给工厂的参数简单地设置为新类的类属性。

为了方便创建类型受限的容器类,我将元素类型与特性字典分开处理。如果没有通过,就会被忽略。

和以前一样,工厂生成的类会被缓存,这样每次调用具有相同特性的类时,都会得到相同的类对象实例。

def template_class(cls, classcache={}):

    def factory(element_type=None, **features):

        key = (cls, element_type) + tuple(features.items())
        if key in classcache:
            return classcache[key]

        newname  = cls.__name__
        if element_type or features:
            newname += "("
            if element_type:
                newname += element_type.__name__
                if features:
                    newname += ", "
            newname += ", ".join(key + "=" + repr(value)
                                 for key, value in features.items())
            newname += ")"

        newclass = type(cls)(newname, (cls,), {})
        if hasattr(newclass, "__classinit__"):
            classinit = getattr(cls.__classinit__, "im_func", cls.__classinit__)
            newclass = classinit(newclass, element_type, features) or newclass
        else:
            if element_type:
                newclass.element_type = element_type
            for key, value in features.items():
                setattr(newclass, key, value)

        classcache[key] = newclass
        return newclass

    factory.__name__ = cls.__name__
    return factory

一个示例类型限制(实际上是类型转换)列表类:

@template_class
class ListOf(list):

    def __classinit__(cls, element_type, features):
        if isinstance(element_type, type):
            cls.element_type = element_type
        else:
            raise TypeError("need element type")

    def __init__(self, iterable):
        for item in iterable:
            try:
                self.append(self.element_type(item))
            except ValueError:
                raise TypeError("value '%s' not convertible to %s"
                        % (item, self.element_type.__name__))

    # etc., to provide type conversion for items added to list 

生成新类:

Floatlist = ListOf(float)
Intlist   = ListOf(int)

然后实例化:

print FloatList((1, 2, 3))       # 1.0, 2.0, 3.0
print IntList((1.0, 2.5, 3.14))  # 1, 2, 3

或者只是创建类并一步实例化:

print ListOf(float)((1, 2, 3))
print ListOf(int)((1.0, 2.5, 3.14))

【讨论】:

  • 我要补充一点,这看起来比您的解决方案要长得多,但大部分额外代码涉及确保生成的类具有反映类中使用的功能的 __name__ 属性。例如,尝试print ListOf(int, convert=True) 将其添加到元类不会减少任何代码。
  • 谢谢!它更长,有些部分看起来有点老套:-P 但最后看起来确实不错!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-12
  • 1970-01-01
  • 2012-06-19
  • 2022-11-24
  • 1970-01-01
  • 2017-05-31
相关资源
最近更新 更多