Python: Unterklasse "Typ" zum Erstellen spezialisierter Typen (z. B. eine "Liste von int")

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

  •  27-10-2019
  •  | 
  •  

Frage

Ich versuche, type in eine Unterklasse zu unterteilen, um eine Klasse zu erstellen, mit der spezielle Typen erstellt werden können. z.B. ein ListType:

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

Dieser ListOfInt wird jedoch niemals zum Erstellen von Instanzen verwendet! Ich verwende es nur als eine Instanz von type, die ich manipulieren kann, um sie mit anderen Typen zu vergleichen ... Insbesondere muss ich in meinem Fall nach einer geeigneten Operation suchen, die der Art der Eingabe entspricht, und ich benötige den Typ um mehr Präzisionen zu enthalten (wie list of int oder XML string usw.).

Also hier ist, was ich mir ausgedacht habe:

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)

Die Verwendung von abc ist im obigen Code nicht offensichtlich. Wenn ich jedoch einen ListType der Unterklasse wie im obigen Beispiel schreiben möchte, wird er nützlich ...

Die Grundfunktionalität funktioniert tatsächlich:

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

Aber wenn ich versuche zu überprüfen, ob t eine Instanz von SpzType ist, flippt Python aus:

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

Ich habe mit pdb.pm() untersucht, was los ist, und festgestellt, dass der folgende Code den Fehler auslöst:

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

WeIrD ?! Offensichtlich gibt es ein Argument ... Was bedeutet das? Irgendeine Idee ? Habe ich abc missbraucht?

War es hilfreich?

Lösung 3

Dank des Kommentars von kindall habe ich den Code wie folgt überarbeitet:

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 ist also im Grunde jetzt eine Unterklasse von abc.ABCMeta, und subclasshook ist als Instanzmethode implementiert.Es funktioniert großartig und ist (IMO) elegant !!!

BEARBEITEN: Es gab eine schwierige Sache ... weil __subclasshook__ eine Klassenmethode sein muss, muss ich die classmethod-Funktion manuell aufrufen ... sonst funktioniert es nicht, wenn ich __subclasshook__ implementieren möchte.

Andere Tipps

Ich bin mir nicht ganz sicher, was Sie erreichen wollen.Vielleicht ist es besser, das collections-Modul zu verwenden, als den abc direkt zu verwenden?

Weitere Informationen zu generischen Sammlungsklassen finden Sie in PEP 3119

Die Art von Dingen, die Sie tun möchten, könnte wahrscheinlich einfacher mit einer Klassenfactory-Funktion wie der folgenden erledigt werden.Zumindest für mich ist es einfacher, die verschiedenen Ebenen, auf denen ich arbeiten möchte, gerade zu halten.

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

Beachten Sie, dass ich hier einen dict als Cache verwende, damit Sie für eine bestimmte Kombination aus Elementtyp und Features dasselbe Klassenobjekt erhalten.Dies macht listOf(int) is listOf(int) immer True.

Hier ist eine Dekorationsversion meiner anderen Antwort, die mit jeder Klasse funktioniert. Der Dekorateur gibt eine Factory-Funktion zurück, die eine Unterklasse der ursprünglichen Klasse mit den gewünschten Attributen zurückgibt. Das Schöne an diesem Ansatz ist, dass keine Metaklasse vorgeschrieben ist, sodass Sie bei Bedarf eine Metaklasse (z. B. ABCMeta) ohne Konflikte verwenden können.

Beachten Sie auch, dass, wenn die Basisklasse eine Metaklasse verwendet, diese Metaklasse verwendet wird, um die generierte Unterklasse zu instanziieren. Wenn Sie möchten, können Sie die gewünschte Metaklasse fest codieren oder, wie Sie wissen, einen Dekorateur schreiben, der aus einer Metaklasse einen Dekorator für Vorlagenklassen macht ... es sind Dekoratoren ganz unten!

Wenn vorhanden, wird einer Klassenmethode __classinit__() die an die Factory übergebenen Argumente übergeben, sodass die Klasse selbst Code zum Überprüfen von Argumenten und Festlegen ihrer Attribute haben kann. (Dies wird nach dem __init__() der Metaklasse aufgerufen.) Wenn __classinit__() eine Klasse zurückgibt, wird diese Klasse von der Factory anstelle der generierten zurückgegeben, sodass Sie die Generierungsprozedur sogar auf diese Weise erweitern können (z. B. für eine typgeprüfte Listenklasse Sie können eine von zwei inneren Klassen zurückgeben, je nachdem, ob die Elemente zum Elementtyp gezwungen werden sollen oder nicht.

Wenn __classinit__() nicht vorhanden ist, werden die an die Factory übergebenen Argumente einfach als Klassenattribute für die neue Klasse festgelegt.

Zur Vereinfachung beim Erstellen typbeschränkter Containerklassen habe ich den Elementtyp getrennt vom Feature-Dikt behandelt. Wenn es nicht bestanden wird, wird es ignoriert.

Wie zuvor werden die von der Factory generierten Klassen zwischengespeichert, sodass Sie jedes Mal, wenn Sie eine Klasse mit denselben Funktionen aufrufen, dieselbe Klassenobjektinstanz erhalten.

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

Eine beispielhafte typbeschränkte (tatsächlich typkonvertierende) Listenklasse:

@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 

Generieren neuer Klassen:

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

Dann instanziieren:

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

Oder erstellen Sie einfach die Klasse und instanziieren Sie in einem Schritt:

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top