Python: sottoclasse `tipo` per creare tipi specializzati (ad esempio una" lista di int ")

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

  •  27-10-2019
  •  | 
  •  

Domanda

Sto provando a sottoclassare type per creare una classe che consenta di costruire tipi specializzati. per esempio. un ListType:

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

Tuttavia, questo ListOfInt non verrà mai utilizzato per creare istanze! Lo uso solo come istanza di type che posso manipolare per confrontarlo con altri tipi ... In particolare, nel mio caso ho bisogno di cercare un'operazione adatta, in base al tipo di input, e ho bisogno del tipo per contenere più precisioni (come list of int o XML string, ecc ...).

Quindi, ecco cosa mi è venuto in mente:

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)

L'uso di abc non è ovvio nel codice sopra ... tuttavia se voglio scrivere una sottoclasse ListType come nell'esempio in alto, allora diventa utile ...

La funzionalità di base funziona effettivamente:

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

Ma quando provo a verificare se t è un'istanza di SpzType, Python va fuori di testa:

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

Ho esplorato con pdb.pm() cosa stava succedendo e ho scoperto che il codice seguente genera l'errore:

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

WeIrD?! Ovviamente c'è un argomento ... Allora cosa significa? Qualche idea ? Ho utilizzato in modo improprio abc?

È stato utile?

Soluzione 3

Grazie al commento di kindall , ho refactoring il codice come segue:

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)

Quindi, fondamentalmente, SpzType è ora una sottoclasse di abc.ABCMeta e subclasshook è implementato come metodo di istanza.Funziona alla grande ed è (IMO) elegante !!!

EDIT: C'era una cosa complicata ... perché __subclasshook__ deve essere un metodo di classe, quindi devo chiamare manualmente la funzione classmethod ... altrimenti non funziona se voglio implementare __subclasshook__.

Altri suggerimenti

Non sono abbastanza sicuro di cosa vuoi ottenere.Forse è meglio usare il modulo collections invece di usare direttamente abc?

Ci sono maggiori informazioni sulle classi di raccolte generiche in PEP 3119

Il genere di cose che vuoi fare potrebbe probabilmente essere fatto più facilmente usando una funzione class factory come la seguente.Almeno per me, rende più semplice mantenere dritti i vari livelli a cui sto cercando di operare.

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

Nota che sto usando un dict come cache qui in modo da ottenere lo stesso oggetto classe per una data combinazione di tipo di elemento e caratteristiche.Questo rende listOf(int) is listOf(int) sempre True.

Ecco una versione decoratore della mia altra risposta che funziona con qualsiasi classe. Il decoratore restituisce una funzione di fabbrica che restituisce una sottoclasse della classe originale con gli attributi desiderati. La cosa bella di questo approccio è che non impone una metaclasse, quindi puoi utilizzare una metaclasse (ad esempio ABCMeta) se lo desideri senza conflitti.

Si noti inoltre che se la classe base utilizza una metaclasse, tale metaclasse verrà utilizzata per istanziare la sottoclasse generata. Potresti, se lo desideri, codificare la metaclasse desiderata o, sai, scrivere un decoratore che trasforma una metaclasse in un decoratore per le classi modello ... sono decoratori fino in fondo!

Se esiste, a un metodo di classe __classinit__() vengono passati gli argomenti passati alla factory, quindi la classe stessa può avere il codice per convalidare gli argomenti e impostare i suoi attributi. (Questo verrebbe chiamato dopo il __init__() della metaclasse.) Se __classinit__() restituisce una classe, questa classe viene restituita dalla factory al posto di quella generata, quindi puoi anche estendere la procedura di generazione in questo modo (ad esempio per una classe di elenco con controllo del tipo , potresti restituire una delle due classi interne a seconda che gli elementi debbano essere costretti o meno al tipo di elemento).

Se __classinit__() non esiste, gli argomenti passati alla factory vengono semplicemente impostati come attributi di classe sulla nuova classe.

Per comodità nella creazione di classi di contenitori limitati al tipo, ho gestito il tipo di elemento separatamente dalla funzionalità dict. Se non viene superato, verrà ignorato.

Come prima, le classi generate dalla factory vengono memorizzate nella cache in modo che ogni volta che si richiama una classe con le stesse caratteristiche, si ottiene la stessa istanza dell'oggetto classe.

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 esempio di classe di elenco con limitazione del tipo (conversione del tipo, in realtà):

@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 

Generazione di nuove classi:

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

Quindi crea un'istanza:

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

Oppure crea semplicemente la classe e istanziala in un unico passaggio:

print ListOf(float)((1, 2, 3))
print ListOf(int)((1.0, 2.5, 3.14))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top