Python:子类“type”来创建专用类型(例如“int列表”)

发布于 2024-11-15 17:03:47 字数 1763 浏览 3 评论 0原文

我正在尝试对 type 进行子类化,以创建一个允许构建专用类型的类。例如 ListType

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

但是,这个 ListOfInt 永远不会用于创建实例!我只是将它用作 type 的实例,我可以对其进行操作以与其他类型进行比较...特别是,在我的情况下,我需要根据类型的类型查找合适的操作输入,并且我需要类型包含更多精度(例如 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)

WeIrD ?!显然有一个争论......那么这是什么意思呢?有什么想法吗?我是否误用了 abc

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 ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

那些过往 2024-11-22 17:03:47

我不太确定你想要实现什么目标。也许使用collections模块比直接使用abc更好?

有关通用集合类的更多信息,请参见 PEP第3119章

I'm not quite sure what you want to achieve. Maybe it is better to use collections module instead of using abc directly?

There is more info about generic collection classes in PEP 3119

东北女汉子 2024-11-22 17:03:47

您想要做的事情可能可以使用如下所示的类工厂函数更轻松地完成。至少对我来说,它使我能够更直接地保持我尝试操作的各个级别。

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

The sort of thing you want to do could probably be done more easily using a class factory function like the following. At least for me, it makes it more straightforward to keep straight the various levels at which I'm trying to operate.

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

Note that I'm using a dict as a cache here so that you get the same class object for a given combination of element type and features. This makes listOf(int) is listOf(int) always True.

会发光的星星闪亮亮i 2024-11-22 17:03:47

感谢 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__

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

望笑 2024-11-22 17:03:47

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

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

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

如果 __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))

Here's a decorator version of my other answer that works with any class. The decorator returns a factory function that returns a subclass of the original class with the desired attributes. The nice thing about this approach is that it does not mandate a metaclass, so you can use a metaclass (e.g. ABCMeta) if desired without conflicts.

Also note that if the base class uses a metaclass, that metaclass will be used to instantiate the generated subclass. You could, if you wanted, hard-code the desired metaclass, or, you know, write a decorator that makes a metaclass into a decorator for template classes... it's decorators all the way down!

If it exists, a class method __classinit__() is passed the arguments passed to the factory, so the class itself can have code to validate arguments and set its attributes. (This would be called after the metaclass's __init__().) If __classinit__() returns a class, this class is returned by the factory in place of the generated one, so you can even extend the generation procedure this way (e.g. for a type-checked list class, you could return one of two inner classes depending on whether the items should be coerced to the element type or not).

If __classinit__() does not exist, the arguments passed to the factory are simply set as class attributes on the new class.

For convenience in creating type-restricted container classes, I have handled the element type separately from the feature dict. If it's not passed, it'll be ignored.

As before, the classes generated by the factory are cached so that each time you call for a class with the same features, you get the same class object instance.

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

An example type-restricted (type-converting, actually) list class:

@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 

Generating new classes:

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

Then instantiate:

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

Or just create the class and instantiate in one step:

print ListOf(float)((1, 2, 3))
print ListOf(int)((1.0, 2.5, 3.14))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文