web-dev-qa-db-fra.com

Quels sont les cas d'utilisation (concrets) des métaclasses?

J'ai un ami qui aime utiliser des métaclasses, et les propose régulièrement comme solution.

Je pense que vous n'avez presque jamais besoin d'utiliser de métaclasses. Pourquoi? parce que je pense que si vous faites quelque chose comme ça à une classe, vous devriez probablement le faire à un objet. Et une petite refonte/refactorisation s'impose.

La possibilité d'utiliser des métaclasses a amené beaucoup de gens dans beaucoup d'endroits à utiliser les classes comme une sorte d'objet de second ordre, ce qui me semble juste désastreux. La programmation doit-elle être remplacée par la méta-programmation? L'ajout de décorateurs de classe l'a malheureusement rendu encore plus acceptable.

Alors, je suis désespéré de connaître vos cas d'utilisation (concrets) valides pour les métaclasses en Python. Ou pour être éclairé sur la raison pour laquelle la mutation des classes est parfois meilleure que la mutation des objets.

Je vais commencer:

Parfois, lors de l'utilisation d'une bibliothèque tierce, il est utile de pouvoir muter la classe d'une certaine manière.

(C'est le seul cas auquel je peux penser, et ce n'est pas concret)

101
Ali Afshar

J'ai une classe qui gère le traçage non interactif, comme interface avec Matplotlib. Cependant, à l'occasion, on veut faire un traçage interactif. Avec seulement quelques fonctions, j'ai constaté que j'étais capable d'augmenter le nombre de chiffres, d'appeler le tirage manuellement, etc., mais je devais le faire avant et après chaque appel de traçage. Donc, pour créer à la fois un wrapper de traçage interactif et un wrapper de traçage hors écran, j'ai trouvé qu'il était plus efficace de le faire via des métaclasses, en encapsulant les méthodes appropriées, que de faire quelque chose comme:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

Cette méthode ne suit pas les modifications de l'API, etc., mais celle qui itère sur les attributs de classe dans __init__ avant de réinitialiser les attributs de classe est plus efficace et maintient les choses à jour:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    Elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

Bien sûr, il pourrait y avoir de meilleures façons de le faire, mais j'ai trouvé que cela était efficace. Bien sûr, cela pourrait également être fait dans __new__ ou __init__, mais c'est la solution que j'ai trouvée la plus simple.

23
Matt

On m'a posé la même question récemment et j'ai trouvé plusieurs réponses. J'espère que c'est OK pour relancer ce fil, car je voulais développer quelques-uns des cas d'utilisation mentionnés et en ajouter quelques nouveaux.

La plupart des métaclasses que j'ai vues font deux choses:

  1. Inscription (ajout d'une classe à une structure de données):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass
    

    Chaque fois que vous sous-classe Model, votre classe est enregistrée dans le dictionnaire models:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}
    

    Cela peut également être fait avec des décorateurs de classe:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass
    

    Ou avec une fonction d'enregistrement explicite:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)
    

    En fait, c'est à peu près la même chose: vous mentionnez défavorablement les décorateurs de classe, mais ce n'est vraiment rien de plus que du sucre syntaxique pour une invocation de fonction sur une classe, donc il n'y a pas de magie à ce sujet.

    Quoi qu'il en soit, l'avantage des métaclasses dans ce cas est l'héritage, car elles fonctionnent pour toutes les sous-classes, tandis que les autres solutions ne fonctionnent que pour les sous-classes explicitement décorées ou enregistrées.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
    
  2. Refactoring (modification des attributs de classe ou ajout de nouveaux):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass
    

    Chaque fois que vous sous-classe Model et définissez des attributs Field, ils sont injectés avec leurs noms (pour des messages d'erreur plus informatifs, par exemple), et regroupés dans un _fields dictionnaire (pour une itération facile, sans avoir à parcourir tous les attributs de classe et tous les attributs de ses classes de base à chaque fois):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}
    

    Encore une fois, cela peut être fait (sans héritage) avec un décorateur de classe:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(
    

    Ou explicitement:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!
    

    Bien que, contrairement à votre plaidoyer pour une programmation non méta lisible et maintenable, c'est beaucoup plus lourd, redondant et sujet aux erreurs:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}
    

Après avoir considéré les cas d'utilisation les plus courants et concrets, les seuls cas où vous DEVEZ absolument utiliser des métaclasses sont lorsque vous souhaitez modifier le nom de la classe ou la liste des classes de base, car une fois définis, ces paramètres sont intégrés dans la classe, et aucun décorateur ou la fonction peut les défaire.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__# foo
print B.__# foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

Cela peut être utile dans les cadres pour émettre des avertissements chaque fois que des classes avec des noms similaires ou des arbres d'héritage incomplets sont définies, mais je ne peux pas penser à une raison à côté de la pêche à la traîne pour changer réellement ces valeurs. Peut-être que David Beazley le peut.

Quoi qu'il en soit, dans Python 3, les métaclasses ont également le __prepare__, qui vous permet d'évaluer le corps de la classe dans un mappage autre qu'un dict, prenant ainsi en charge les attributs ordonnés, les attributs surchargés et d'autres trucs sympas:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

Vous pourriez faire valoir que les attributs ordonnés peuvent être obtenus avec des compteurs de création, et la surcharge peut être simulée avec des arguments par défaut:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

En plus d'être beaucoup plus laid, il est également moins flexible: que faire si vous voulez des attributs littéraux ordonnés, comme des entiers et des chaînes? Que faire si None est une valeur valide pour x?

Voici une façon créative de résoudre le premier problème:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

Et voici une façon créative de résoudre le second:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

Mais c'est beaucoup, beaucoup plus vaudou qu'une simple métaclasse (surtout la première, qui fait vraiment fondre votre cerveau). Mon point est que vous considérez les métaclasses comme inconnues et contre-intuitives, mais vous pouvez également les considérer comme la prochaine étape de l'évolution des langages de programmation: il vous suffit d'ajuster votre état d'esprit. Après tout, vous pourriez probablement tout faire en C, y compris définir une structure avec des pointeurs de fonction et la passer comme premier argument à ses fonctions. Une personne qui voit C++ pour la première fois pourrait dire: "quelle est cette magie? Pourquoi le compilateur passe-t-il implicitement this aux méthodes, mais pas aux fonctions régulières et statiques? Il vaut mieux être explicite et bavard sur vos arguments ". Mais alors, la programmation orientée objet est beaucoup plus puissante une fois que vous l'avez obtenue; et il en est de même, euh ... de la programmation orientée quasi-aspect, je suppose. Et une fois que vous comprenez les métaclasses, elles sont en fait très simples, alors pourquoi ne pas les utiliser quand c'est pratique?

Et enfin, les métaclasses sont rad, et la programmation devrait être amusante. Utiliser des constructions de programmation et des modèles de conception standard tout le temps est ennuyeux et sans intérêt, et entrave votre imagination. Vis un peu! Voici une métamétaclasse, rien que pour vous.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan
83
Dan Gittik

Le but des métaclasses n'est pas de remplacer la distinction classe/objet par métaclasse/classe - c'est de changer le comportement des définitions de classe (et donc de leurs instances) d'une manière ou d'une autre. Il s'agit en fait de modifier le comportement de l'instruction de classe d'une manière qui peut être plus utile pour votre domaine particulier que la valeur par défaut. Les choses pour lesquelles je les ai utilisées sont:

  • Suivi des sous-classes, généralement pour enregistrer les gestionnaires. C'est pratique lorsque vous utilisez une configuration de style plugin, où vous souhaitez enregistrer un gestionnaire pour une chose particulière simplement en sous-classant et en configurant quelques attributs de classe. par exemple. supposons que vous écriviez un gestionnaire pour différents formats de musique, où chaque classe implémente des méthodes appropriées (lecture/extraction de balises, etc.) pour son type. L'ajout d'un gestionnaire pour un nouveau type devient:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here
    

    La métaclasse maintient alors un dictionnaire de {'.mp3' : MP3File, ... } etc, et construit un objet du type approprié lorsque vous demandez un gestionnaire via une fonction d'usine.

  • Changement de comportement. Vous souhaiterez peut-être attacher une signification spéciale à certains attributs, ce qui entraînera une modification du comportement lorsqu'ils seront présents. Par exemple, vous pouvez rechercher des méthodes portant le nom _get_foo et _set_foo et les convertir de manière transparente en propriétés. Comme exemple dans le monde réel, voici une recette que j'ai écrite pour donner plus de définitions de structures de type C. La métaclasse est utilisée pour convertir les éléments déclarés en une chaîne au format struct, en gérant l'héritage, etc., et produire une classe capable de gérer cela.

    Pour d'autres exemples du monde réel, jetez un œil à divers ORM, comme sqlalchemy's ORM ou sqlobject . Encore une fois, le but est d'interpréter les définitions (ici les définitions des colonnes SQL) avec une signification particulière.

35
Brian

Commençons par la citation classique de Tim Peter:

Les métaclasses sont une magie plus profonde que 99% des utilisateurs devraient s'inquiéter. Si vous vous demandez si vous en avez besoin, vous n'en avez pas (les personnes qui en ont réellement besoin savent avec certitude qu'elles en ont besoin, et n'ont pas besoin d'explication pour savoir pourquoi). Tim Peters (c.l.p post 2002-12-22)

Cela dit, j'ai (périodiquement) rencontré de véritables utilisations des métaclasses. Celui qui me vient à l'esprit se trouve dans Django où tous vos modèles héritent de models.Model. Models.Model, à son tour, fait de la magie sérieuse pour envelopper vos modèles DB avec la bonté ORM de Django. Cette magie opère au moyen de métaclasses. Elle crée toutes sortes de classes d'exception, de classes de gestionnaire, etc. etc.

Voir Django/db/models/base.py, classe ModelBase () pour le début de l'histoire.

17
Peter Rowell

Un modèle raisonnable d'utilisation des métaclasses fait quelque chose une fois quand une classe est définie plutôt que de façon répétée chaque fois que la même classe est instanciée.

Lorsque plusieurs classes partagent le même comportement spécial, en répétant __metaclass__=X est évidemment mieux que de répéter le code spécial et/ou d'introduire des superclasses partagées ad hoc.

Mais même avec une seule classe spéciale et aucune extension prévisible, __new__ et __init__ d'une métaclasse est un moyen plus propre d'initialiser des variables de classe ou d'autres données globales que de mélanger du code spécial et des instructions normales def et class dans le corps de définition de classe.

6
user412090

Les métaclasses peuvent être utiles pour la construction de langages spécifiques à un domaine en Python. Des exemples concrets sont Django, la syntaxe déclarative de SQLObject des schémas de base de données.

Un exemple de base de A Conservative Metaclass par Ian Bicking:

Les métaclasses que j'ai utilisées étaient principalement destinées à prendre en charge une sorte de style déclaratif de programmation. Par exemple, considérez un schéma de validation:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

Quelques autres techniques: Ingrédients pour construire une DSL en Python (pdf).

Edit (par ALi): Un exemple de faire cela en utilisant des collections et des instances est ce que je préférerais. Le fait important est les instances, qui vous donnent plus de puissance et éliminent la raison d'utiliser des métaclasses. Il convient également de noter que votre exemple utilise un mélange de classes et d'instances, ce qui est certainement une indication que vous ne pouvez pas tout faire avec des métaclasses. Et crée une façon vraiment non uniforme de le faire.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

Ce n'est pas parfait, mais il n'y a déjà presque aucune magie, pas besoin de métaclasses et une uniformité améliorée.

6
jfs

La seule fois où j'ai utilisé des métaclasses en Python était lors de l'écriture d'un wrapper pour l'API Flickr.

Mon objectif était de gratter site api de flickr et de générer dynamiquement une hiérarchie de classes complète pour permettre l'accès à l'API en utilisant Python objets:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

Donc, dans cet exemple, parce que j'ai généré l'intégralité de l'API Python Flickr à partir du site Web, je ne connais vraiment pas les définitions de classe au moment de l'exécution. Pouvoir générer dynamiquement des types était très utile.

5
Triptych

Hier, je pensais à la même chose et je suis tout à fait d'accord. Les complications du code causées par des tentatives de le rendre plus déclaratif rendent généralement la base de code plus difficile à maintenir, plus difficile à lire et moins Pythonic à mon avis. Cela nécessite également normalement beaucoup de copy.copy () ing (pour maintenir l'héritage et copier de la classe à l'instance) et signifie que vous devez regarder à de nombreux endroits pour voir ce qui se passe (toujours à partir de la métaclasse), ce qui va à l'encontre de la python grain également. J'ai parcouru le code formencode et sqlalchemy pour voir si un tel style déclaratif en valait la peine et ce n'est clairement pas le cas. Ce style devrait être laissé aux descripteurs (tels que les propriétés et les méthodes ) et des données immuables. Ruby a un meilleur support pour de tels styles déclaratifs et je suis content que le langage de base python) ne passe pas dans cette voie.

Je peux voir leur utilisation pour le débogage, ajoutez une métaclasse à toutes vos classes de base pour obtenir des informations plus riches. Je vois également leur utilisation uniquement dans les (très) grands projets pour se débarrasser de certains codes passe-partout (mais à perte de clarté). sqlalchemy pour exemple les utilise ailleurs, pour ajouter une méthode personnalisée particulière à toutes les sous-classes en fonction d'une valeur d'attribut dans leur définition de classe, par exemple un exemple de jouet

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

pourrait avoir une métaclasse qui a généré une méthode dans cette classe avec des propriétés spéciales basées sur "bonjour" (disons une méthode qui a ajouté "bonjour" à la fin d'une chaîne). Il pourrait être bon pour la maintenabilité de vous assurer que vous n'avez pas eu à écrire une méthode dans chaque sous-classe que vous créez à la place, tout ce que vous avez à définir est method_maker_value.

Cependant, le besoin est si rare et ne réduit qu'un peu de frappe que cela ne vaut vraiment la peine d'être pris en compte que si vous avez une base de code suffisamment grande.

5
David Raznick

Vous n'avez jamais absolument besoin d'utiliser une métaclasse, car vous pouvez toujours construire une classe qui fait ce que vous voulez en utilisant l'héritage ou l'agrégation de la classe que vous souhaitez modifier .

Cela dit, cela peut être très pratique dans Smalltalk et Ruby pour pouvoir modifier une classe existante, mais Python n'aime pas le faire directement).

Il y a un excellent article DeveloperWorks sur les métaclasses dans Python qui pourrait aider. Le article Wikipedia est également assez bon.

4
Charlie Martin

La façon dont j'ai utilisé les métaclasses était de fournir des attributs aux classes. Prends pour exemple:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

mettra l'attribut name sur chaque classe qui aura la métaclasse définie pour pointer vers NameClass.

3
hyperboreean

Les métaclasses ne remplacent pas la programmation! Ce n'est qu'un truc qui peut automatiser ou rendre plus élégantes certaines tâches. Un bon exemple de ceci est Pygments bibliothèque de coloration syntaxique. Il a une classe appelée RegexLexer qui permet à l'utilisateur de définir un ensemble de règles de lexing en tant qu'expressions régulières sur une classe. Une métaclasse est utilisée pour transformer les définitions en un analyseur utile.

Ils sont comme du sel; c'est trop facile à utiliser.

3
Benjamin Peterson

Le seul cas d'utilisation légitime d'une métaclasse est d'empêcher d'autres développeurs curieux de toucher votre code. Une fois qu'un développeur curieux maîtrise les métaclasses et commence à fouiner avec le vôtre, ajoutez un autre niveau ou deux pour les garder à l'écart. Si cela ne fonctionne pas, commencez à utiliser type.__new__ ou peut-être un schéma utilisant une métaclasse récursive.

(écrit la langue dans la joue, mais j'ai vu ce genre d'obscurcissement fait. Django est un parfait exemple)

3
Mike A

Certaines bibliothèques GUI ont des problèmes lorsque plusieurs threads tentent d'interagir avec eux. tkinter en est un exemple; et bien que l'on puisse gérer explicitement le problème avec les événements et les files d'attente, il peut être beaucoup plus simple d'utiliser la bibliothèque d'une manière qui ignore complètement le problème. Voici - la magie des métaclasses.

La possibilité de réécrire dynamiquement une bibliothèque entière de manière transparente afin qu'elle fonctionne correctement comme prévu dans une application multithread peut être extrêmement utile dans certaines circonstances. Le module safetkinter le fait à l'aide d'une métaclasse fournie par le module threadbox - les événements et les files d'attente ne sont pas nécessaires.

Un aspect intéressant de threadbox est qu'il ne se soucie pas de la classe qu'il clone. Il fournit un exemple de la façon dont toutes les classes de base peuvent être touchées par une métaclasse si nécessaire. Un autre avantage fourni par les métaclasses est qu'elles s'exécutent également sur les classes héritées. Des programmes qui s’écrivent - pourquoi pas?

3
Noctis Skytower

C'est une utilisation mineure, mais ... une chose pour laquelle j'ai trouvé des métaclasses utiles est d'invoquer une fonction chaque fois qu'une sous-classe est créée. J'ai codifié cela en une métaclasse qui recherche un attribut __initsubclass__: Chaque fois qu'une sous-classe est créée, toutes les classes parentes qui définissent cette méthode sont appelées avec __initsubclass__(cls, subcls). Cela permet la création d'une classe parente qui enregistre ensuite toutes les sous-classes avec un registre global, exécute des vérifications invariantes sur les sous-classes chaque fois qu'elles sont définies, effectue des opérations de liaison tardive, etc ... le tout sans avoir à appeler manuellement les fonctions o pour créer des métaclasses personnalisées qui exécutent chacune de ces tâches distinctes.

Remarquez, je me suis lentement rendu compte que la magie implicite de ce comportement est quelque peu indésirable, car elle est inattendue si vous regardez une définition de classe hors contexte ... et donc je me suis éloigné de l'utilisation de cette solution pour autre chose de sérieux initialiser un attribut __super pour chaque classe et instance.

2
Eli Collins

Il semble y avoir une utilisation légitime décrite ici - Réécriture Python Docstrings avec une métaclasse.

1
mistermarko

J'ai récemment dû utiliser une métaclasse pour aider à définir de manière déclarative un modèle SQLAlchemy autour d'une table de base de données remplie de données de recensement américain de http://census.ire.org/data/bulkdata.html

IRE fournit coquilles de base de données pour les tableaux de données du recensement, qui créent des colonnes entières selon une convention de dénomination du Census Bureau de p012015, p012016, p012017, etc.

Je voulais a) pouvoir accéder à ces colonnes en utilisant un model_instance.p012017 syntaxe, b) être assez explicite sur ce que je faisais et c) ne pas avoir à définir explicitement des dizaines de champs sur le modèle, j'ai donc sous-classifié DeclarativeMeta de SQLAlchemy pour parcourir une plage de colonnes et créer automatiquement champs de modèle correspondant aux colonnes:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Je pourrais alors utiliser cette métaclasse pour ma définition de modèle et accéder aux champs énumérés automatiquement sur le modèle:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __table= 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...
1
Geoffrey Hing

Un autre cas d'utilisation est lorsque vous souhaitez pouvoir modifier les attributs au niveau de la classe et être sûr que cela n'affecte que l'objet à portée de main. En pratique, cela implique de "fusionner" les phases des métaclasses et des instanciations de classes, vous amenant ainsi à ne traiter que les instances de classe de leur propre (unique) type.

Je devais aussi le faire quand (pour des questions de lisibilité et polymorphisme ) nous voulions définir dynamiquementproperty s qui ont renvoyé des valeurs (peuvent) résulter de calculs basés sur (souvent changeants) des attributs de niveau instance, qui ne peut être fait qu'au niveau de la classe, ie après l'instanciation de la métaclasse et avant l'instanciation de la classe.

0
keepAlive

J'ai dû les utiliser une fois pour un analyseur binaire pour le rendre plus facile à utiliser. Vous définissez une classe de message avec les attributs des champs présents sur le fil. Ils devaient être commandés de la manière dont ils ont été déclarés pour en construire le format de fil final. Vous pouvez le faire avec des métaclasses, si vous utilisez un dict d'espace de noms ordonné. En fait, c'est dans les exemples de métaclasses:

https://docs.python.org/3/reference/datamodel.html#metaclass-example

Mais en général: évaluer très attentivement, si vous avez vraiment vraiment besoin de la complexité supplémentaire des métaclasses.

0
GeeF

la réponse de @Dan Gittik est cool

les exemples à la fin pourraient clarifier beaucoup de choses, je l'ai changé en python 3 et donne quelques explications:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan

  • tout est objet, donc la classe est objet
  • l'objet de classe est créé par la métaclasse
  • toutes les classes héritées du type sont des métaclasses
  • métaclasse pourrait contrôler la création de classe
  • la métaclasse pourrait également contrôler la création de la métaclasse (donc elle pourrait boucler pour toujours)
  • c'est la métaprogrammation ... vous pouvez contrôler le système de type au moment de l'exécution
  • encore une fois, tout est objet, c'est un système uniforme, tapez create type et type create instance
0
foolcage