2010-10-12 23 views
9

Je suis en train d'écrire un métaclasse générique pour les sous-classes de suivigénérique Python métaclasse pour garder la trace des sous-classes

Depuis que je veux que ce soit générique, je ne voulais pas coder en dur un nom de classe dans cette métaclasse, donc Je suis venu avec une fonction qui génère la métaclasse appropriée, quelque chose comme:

def make_subtracker(root): 
    class SubclassTracker(type): 
     def __init__(cls, name, bases, dct): 
      print('registering %s' % (name,)) 
      root._registry.append(cls) 
      super(SubclassTracker, cls).__init__(name, bases, dct) 
    return SubclassTracker 

de cette façon, je pouvais l'appeler pour générer un métaclasse pour une classe racine spécifique:

__metaclass__ = make_subtracker(Root) 

Voici où je tombe sur un problème. Je ne peux pas le faire:

class Root(object): 
    _registry = [] 
    __metaclass__ = make_subtracker(Root) 

... parce que Root ne définit pas encore quand je l'utilise make_subtracker(Root). J'ai essayé d'ajouter le métaclasse attribut plus tard, de sorte qu'au moins il peut être appliqué dans les sous-classes:

class Root(object): 
    _registry = [] 

Root.__metaclass__ = make_subtracker(Root) 

... mais cela ne fonctionne pas. métaclasse a un traitement spécial lorsque la définition de la classe est lu, tel que défini dans http://docs.python.org/reference/datamodel.html#customizing-class-creation

Je cherche des suggestions afin de le faire (soit changer une classe métaclasse lors de l'exécution d'une manière qu'il est appliqué à ses sous-classes, ou toute autre alternative).

+1

Veuillez ne pas faire ceci. Les gens qui viendront après vous l'arracheront parce que c'est trop complexe. Veuillez utiliser une fonction d'usine qui crée des objets de la sous-classe appropriée. –

Répondre

8

Python fait automatiquement pour les classes de type nouveau, comme mentionné dans ce answer à la queston How can I find all subclasses of a given class in Python? similaire ici.

+1

Bien que techniquement, cela reste en suivi, en fonction de votre utilisation, il peut ne pas être très efficace. Il renvoie une liste, donc si vous recherchez une classe avec des attributs spécifiques, vous devrez parcourir toute la liste. L'utilisation d'une méta-classe vous permet d'indexer efficacement les sous-classes comme vous le souhaitez. – Cerin

+0

@Cerin: Peut-être ... si la haute efficacité est une préoccupation ou un problème - qui ne peut être déterminé à partir de la question qui ne précise pas comment l'information sera utilisée. Quoi qu'il en soit, je serais intéressé de voir une implémentation concrète de vos idées puisque toutes les autres réponses sont actuellement aussi basées sur des listes - alors n'hésitez pas à en ajouter une. – martineau

+0

@martinaeu, Mon implémentation est la même que celle d'aaronasterling, sauf que le registre est un dictionnaire au lieu d'une liste, et la clé est la représentation hachée que vous voulez utiliser pour chaque classe. Je l'utilise actuellement comme une alternative au modèle register() dans Django, pour associer une classe ModelForm à un slug unique, pour un envoi rapide d'URL. – Cerin

8

Je pense que vous voulez quelque chose comme ça (non testé):

class SubclassTracker(type): 
    def __init__(cls, name, bases, dct): 
     if not hasattr(cls, '_registry'): 
      cls._registry = [] 
     print('registering %s' % (name,)) 
     cls._registry.append(cls) 
     super(SubclassTracker, cls).__init__(name, bases, dct) 

Ensuite, pour Python 2, vous pouvez l'appeler comme:

class Root(object): 
    __metaclass__ = SubclassTracker 

pour Python 3

class Root(object, metaclass=SubclassTracker): 

Notez que vous n'avez pas besoin de coller l'attribut _registry là-bas parce que ce genre de choses est ce que metaclas ses sont pour. Puisque vous en avez déjà un autour ...;)

Notez également que vous pouvez déplacer le code d'enregistrement dans une clause else afin que la classe ne s'enregistre pas en tant que sous-classe.

+0

Il y a une erreur de syntaxe dans le type (metaclass.root._registry.append (cls) ... Je l'ai changé pour: 'cls._registry.append (cls)' qui fonctionne –

+0

@Carles C'était une erreur de syntaxe moche. Comme une poubelle complète et totale, j'ai édité plusieurs fois, donc c'était quelques idées différentes pour quelques lignes différentes qui auraient toutes dû être effacées, je suis désolée pour ça, ça a l'air bien maintenant, merci de l'avoir signalé. – aaronasterling

+0

En fait, puisque vous SubclassTracker n'inclut aucune référence codée en dur au type * root *, il n'y a pas besoin d'une fonction pour le créer, donc j'ai directement défini une classe SubclassTracker et j'ai complètement oublié make_subtracker. Fonctionne bien, merci! –

1

est ici quelque chose que je jouais avec (qui fonctionne):