Python: sous-classe `type` pour créer des types spécialisés (par exemple une « liste d'int »)
Question
Je suis en train de sous-classe type
afin de créer une classe permettant de construire des types spécialisés. par exemple. un ListType
:
>>> ListOfInt = ListType(list, value_type=int)
>>> issubclass(ListOfInt, list)
True
>>> issubclass(list, ListOfInt)
False
>>> # And so on ...
Cependant, cette ListOfInt
ne sera jamais utilisé pour créer des instances! Je viens de l'utiliser comme une instance de type
que je peux manipuler pour comparer avec d'autres types ... En particulier, dans mon cas, je dois consulter pour une opération appropriée, en fonction du type d'entrée, et j'ai besoin du type pour contenir plus de précisions (comme list of int
ou XML string
, etc ...).
Alors, voici ce que je suis venu avec:
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'utilisation de abc
n'est pas évident dans le code ci-dessus ... si je veux écrire un ListType
de sous-classe comme dans l'exemple ci-dessus, il devient utile ...
La fonctionnalité de base fonctionne réellement:
>>> class SimpleType(SpzType): pass
>>> t = SimpleType(int)
>>> issubclass(t, int)
True
>>> issubclass(int, t)
False
Mais lorsque je tente de vérifier si t
est une instance de SpzType
, Freaks Python sur:
>>> isinstance(t, SpzType)
TypeError: __subclasscheck__() takes exactly one argument (0 given)
J'exploré avec pdb.pm()
ce qui se passait, et je trouve que le code suivant génère l'erreur:
>>> SpzType.__subclasscheck__(SimpleType)
TypeError: __subclasscheck__() takes exactly one argument (0 given)
ÉTRANGE! De toute évidence, il y a un argument ... Alors qu'est-ce que cela veut dire? Une idée ? Ai-je mauvais usage abc
?
La solution 3
Merci à commentaires de Kindall , je refondus le code à ce qui suit:
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)
Donc, fondamentalement, SpzType
est maintenant une sous-classe de abc.ABCMeta
et subclasshook est mis en œuvre une méthode d'instance. Il fonctionne très bien et il est (OMI) élégante !!!
EDIT:. Il y avait une chose délicate ... parce que les besoins de __subclasshook__
être un classmethod, donc je dois appeler la fonction classmethod manuellement ... sinon il ne fonctionne pas si je veux mettre en œuvre __subclasshook__
Autres conseils
Je ne suis pas tout à fait sûr de ce que vous voulez atteindre. Peut-être qu'il est préférable d'utiliser le module de collections
au lieu d'utiliser directement abc
?
Voici une version de décorateur de mon autre réponse qui fonctionne avec toute la classe. Le décorateur retourne une fonction d'usine qui retourne une sous-classe de la classe d'origine avec les attributs souhaités. La bonne chose au sujet de cette approche est qu'elle ne rend pas obligatoire une métaclasse, vous pouvez utiliser une méta-classe (par exemple ABCMeta
) si on le souhaite sans conflits.
Notez également que si la classe de base utilise une méta-classe, qui métaclasse sera utilisé pour instancier la sous-classe générée. Vous pouvez, si vous vouliez, coder en dur l'métaclasse désiré, ou, vous savez, écrire un décorateur qui fait une métaclasse dans un décorateur pour les classes de modèle ... c'est décorateurs tout en bas!
Si elle existe, une méthode de classe __classinit__()
est passé avec succès les arguments passés à l'usine, de sorte que la classe elle-même peut avoir le code à des arguments validate et définir ses attributs. (Ce serait appelé après la __init__()
du métaclasse.) Si __classinit__()
retourne une classe, cette classe est retournée par l'usine à la place de celui généré, vous pouvez même prolonger la procédure de génération de cette façon (par exemple pour une classe de liste vérifiée type , vous pouvez retourner l'une des deux classes internes selon que les articles doivent être sous la contrainte au type d'élément ou non).
Si __classinit__()
n'existe pas, les arguments transmis à l'usine sont simplement définie comme attributs de classe sur la nouvelle classe.
Pour plus de commodité dans la création de classes de conteneurs de type restreint, j'ai manipulé le type d'élément séparément de la fonction dict. Si ce n'est pas passé, il sera ignoré.
Comme précédemment, les classes générées par l'usine sont mises en cache de sorte que chaque fois que vous appelez pour une classe avec les mêmes caractéristiques, vous obtenez la même instance d'objet de 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 exemple type restreint (type de conversion, en fait) classe de liste:
@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
Génération de nouvelles classes:
Floatlist = ListOf(float)
Intlist = ListOf(int)
Alors instancier:
print FloatList((1, 2, 3)) # 1.0, 2.0, 3.0
print IntList((1.0, 2.5, 3.14)) # 1, 2, 3
Ou tout simplement créer la classe et instancier en une seule étape:
print ListOf(float)((1, 2, 3))
print ListOf(int)((1.0, 2.5, 3.14))