Pitón:subclase `tipo` para crear tipos especializados (p. ej.una “lista de int”)

StackOverflow https://stackoverflow.com/questions/6332342

  •  27-10-2019
  •  | 
  •  

Pregunta

Estoy intentando subclasificar type para crear una clase que permita construir tipos especializados.p.ej.a ListType :

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

Sin embargo, esto ListOfInt ¡Nunca se utilizará para crear instancias!Sólo lo uso como una instancia de type que puedo manipular para comparar con otros tipos...En particular, en mi caso necesito buscar una operación adecuada, según el tipo de entrada, y necesito que el tipo contenga más precisiones (como list of int o XML string, etc ...).

Así que esto es lo que se me ocurrió:

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)

El uso de abc no es obvio en el código anterior...sin embargo, si quiero escribir una subclase ListType como en el ejemplo de arriba, entonces resulta útil...

La funcionalidad básica realmente funciona:

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

Pero cuando trato de comprobar si t es una instancia de SpzType, Python se asusta:

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

exploré con pdb.pm() qué estaba pasando y descubrí que el siguiente código genera el error:

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

Extraño ?!Obviamente hay un argumento...Entonces que significa eso ?Alguna idea ?¿Usé mal? abc ?

¿Fue útil?

Solución 3

Gracias por comentar de amable, He refactorizado el código a lo siguiente:

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)

Así que básicamente, SpzType ahora es una subclase de abc.ABCMeta, y gancho de subclase se implementa como un método de instancia.¡Funciona muy bien y es (en mi opinión) elegante!

EDITAR :Había algo complicado...porque __subclasshook__ necesita ser un método de clase, así que tengo que llamar a la función método de clase manualmente...de lo contrario no funciona si quiero implementar __subclasshook__.

Otros consejos

No estoy muy seguro de lo que quieres lograr.Tal vez sea mejor usar collections módulo en lugar de usar abc ¿directamente?

Hay más información sobre las clases de colección genéricas en PEP 3119

El tipo de cosas que desea hacer probablemente podría hacerse más fácilmente usando una función de fábrica de clases como la siguiente.Al menos para mí, hace que sea más sencillo mantener claros los distintos niveles en los que intento operar.

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

Tenga en cuenta que estoy usando un dict como caché aquí para que obtenga el mismo objeto de clase para una combinación determinada de tipo de elemento y características.Esto hace listOf(int) is listOf(int) siempre True.

Aquí hay una versión decorada de mi otra respuesta que funciona con cualquier clase.El decorador devuelve una función de fábrica que devuelve una subclase de la clase original con los atributos deseados.Lo bueno de este enfoque es que no exige una metaclase, por lo que puedes usar una metaclase (p. ej. ABCMeta) si se desea sin conflictos.

También tenga en cuenta que si la clase base usa una metaclase, esa metaclase se usará para crear una instancia de la subclase generada.Podrías, si quisieras, codificar la metaclase deseada o, ya sabes, escribir un decorador que convierta una metaclase en un decorador para clases de plantilla...¡Son decoradores hasta el final!

Si existe, un método de clase. __classinit__() Se pasan los argumentos pasados ​​a la fábrica, por lo que la propia clase puede tener código para validar los argumentos y establecer sus atributos.(Esto se llamaría después de la metaclase __init__().) Si __classinit__() devuelve una clase, esta clase es devuelta por la fábrica en lugar de la generada, por lo que incluso puedes extender el procedimiento de generación de esta manera (p. ej.para una clase de lista de tipo verificado, puede devolver una de dos clases internas dependiendo de si los elementos deben ser forzados al tipo de elemento o no).

Si __classinit__() no existe, los argumentos pasados ​​a la fábrica simplemente se establecen como atributos de clase en la nueva clase.

Para mayor comodidad al crear clases contenedoras de tipo restringido, he manejado el tipo de elemento por separado del dictado de características.Si no se aprueba, será ignorado.

Como antes, las clases generadas por la fábrica se almacenan en caché para que cada vez que llame a una clase con las mismas características, obtenga la misma instancia de objeto de clase.

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

Un ejemplo de clase de lista de tipo restringido (en realidad, de conversión de tipo):

@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 

Generando nuevas clases:

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

Luego crea una instancia:

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

O simplemente cree la clase y cree una instancia en un solo paso:

print ListOf(float)((1, 2, 3))
print ListOf(int)((1.0, 2.5, 3.14))
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top