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

118

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

Je suis d'avis que vous n'avez presque jamais besoin d'utiliser des 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 / refactor est en ordre.

Être capable d'utiliser des métaclasses a poussé beaucoup de gens dans de nombreux endroits à utiliser les classes comme une sorte d'objet de second ordre, ce qui me semble désastreux. La programmation est-elle à remplacer par la méta-programmation? L'ajout de décorateurs de classe l'a malheureusement rendu encore plus acceptable.

Alors s'il vous plaît, 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 les raisons pour lesquelles la mutation de classes est parfois préférable à la mutation d'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 puisse penser, et ce n'est pas concret)

Ali Afshar
la source
3
c'est une excellente question. À en juger par les réponses ci-dessous, il est tout à fait clair qu'il n'y a pas d'utilisation concrète des métaclasses.
Marcus Ottosson

Réponses:

25

J'ai une classe qui gère le traçage non interactif, en tant qu'interface de Matplotlib. Cependant, à l'occasion, on veut faire du traçage interactif. Avec seulement quelques fonctions, j'ai trouvé que j'étais capable d'incrémenter le nombre de chiffres, d'appeler dessiner manuellement, etc., mais je devais les 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 enveloppant 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 changements d'API et ainsi de suite, mais une méthode qui itère sur les attributs de classe __init__avant de redéfinir 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 existe peut-être de meilleures façons de le faire, mais j'ai trouvé que c'é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.

Mat
la source
103

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

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

  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 Model- classez , votre classe est enregistrée dans le modelsdictionnaire:

    >>> 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, alors 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 Model- classez et définissez des Fieldattributs, ils sont injectés avec leurs noms (pour des messages d'erreur plus informatifs, par exemple), et regroupés dans un _fieldsdictionnaire (pour une itération facile, sans avoir à parcourir tous les attributs de classe et toutes ses classes de base '' attributs à 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, cela est beaucoup plus encombrant, 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 examiné les cas d'utilisation les plus courants et les plus concrets, les seuls cas où vous DEVEZ absolument utiliser des métaclasses sont ceux où 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 à 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.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

Cela peut être utile dans les frameworks 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 autre que le trolling 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 la __prepare__méthode, qui vous permet d'évaluer le corps de la classe dans un mappage autre que a dict, prenant ainsi en charge les attributs ordonnés, les attributs surchargés et d'autres trucs super 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 affirmer que les attributs ordonnés peuvent être obtenus avec des compteurs de création et que 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? Et si Noneest 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 vaudou-euh 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 transmettre comme premier argument à ses fonctions. Une personne qui voit C ++ pour la première fois pourrait dire: "Qu'est-ce que cette magie? Pourquoi le compilateur passe-t-il implicitementthisaux méthodes, mais pas aux fonctions régulières et statiques? Il vaut mieux être explicite et verbeux à propos de vos arguments. "Mais alors, la programmation orientée objet est beaucoup plus puissante une fois que vous l'avez; et c'est aussi, euh ... programmation quasi-orientée aspect, je suppose. Et une fois que vous comprendre les métaclasses, elles sont en fait très simples, alors pourquoi ne pas les utiliser quand cela vous convient?

Et enfin, les métaclasses sont géniales et la programmation doit être amusante. Utiliser en permanence des constructions de programmation et des modèles de conception standard est ennuyeux et sans intérêt et entrave votre imagination. Vis un peu! Voici une métamétaclasse, juste 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

Éditer

C'est une question assez ancienne, mais elle suscite toujours des votes positifs, alors j'ai pensé ajouter un lien vers une réponse plus complète. Si vous souhaitez en savoir plus sur les métaclasses et leurs utilisations, je viens de publier un article à ce sujet ici .

Dan Gittik
la source
5
C'est une excellente réponse, merci pour le temps de l'écrire et de donner de multiples exemples
Chen A.
"... l'avantage des métaclasses dans ce cas est l'héritage, car elles fonctionnent pour toutes les sous-classes" - pas en Python 3, je suppose? Je pense que cela fonctionne dans Python 2 uniquement parce que toute classe enfant hérite de l' __metaclass__attribut, mais cet attribut n'est plus spécial en Python 3. Existe-t-il un moyen de faire fonctionner cette chose "les classes enfants sont également construites par la métaclasse du parent" dans Python 3 ?
ForceBru
2
Cela est également vrai pour Python 3, car une classe B, héritant de A, dont la métaclasse est M, est également un type de M. Donc, lorsque B est évalué, M est invoqué pour le créer, et cela vous permet effectivement pour "travailler sur toutes les sous-classes" (de A). Cela dit, Python 3.6 a introduit le plus simple init_subclass, vous pouvez donc désormais manipuler des sous-classes dans une classe de base et ne plus avoir besoin d'une métaclasse à cette fin.
Dan Gittik
C'est génial, j'ai lu tellement d'articles de blog sur les métaclasses, seul celui-ci fait connaître les avantages et les inconvénients et les alternatives aux métaclasses.
ospider
36

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 certaine manière. En fait, il s'agit 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. Ceci 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 / récupération 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 gère ensuite un dictionnaire de {'.mp3' : MP3File, ... }etc et construit un objet du type approprié lorsque vous demandez un gestionnaire via une fonction de fabrique.

  • Changer de comportement. Vous souhaiterez peut-être attribuer une signification particulière à certains attributs, ce qui entraînera une modification du comportement lorsqu'ils sont présents. Par exemple, vous souhaiterez peut-être rechercher des méthodes portant le nom _get_fooet _set_fooet les convertir de manière transparente en propriétés. À titre d'exemple dans le monde réel, voici une recette que j'ai écrite pour donner plus de définitions de structure de type C. La métaclasse est utilisée pour convertir les éléments déclarés en une chaîne de format struct, gérer l'héritage, etc., et produire une classe capable de le gérer.

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

Brian
la source
3
Eh bien, oui, le suivi des sous-classes. Mais pourquoi voudriez-vous cela? Votre exemple est simplement implicite pour register_music_file (Mp3File, ['.mp3']), et la manière explicite est plus lisible et maintenable. Voici un exemple des mauvais cas dont je parle.
Ali Afshar
À propos du cas ORM, parlez-vous de la manière basée sur les classes de définir des tables, ou des métaclasses sur les objets mappés. Parce que SQLAlchemy peut (à juste titre) mapper à n'importe quelle classe (et je suppose qu'il n'utilise pas de métaclasse pour cette activité).
Ali Afshar
10
Je préfère le style plus déclaratif, plutôt que d'exiger des méthodes d'enregistrement supplémentaires pour chaque sous-classe - mieux si tout est enveloppé dans un seul emplacement.
Brian
Pour sqlalchemy, je pense principalement à la couche déclarative, donc peut-être que sqlobject est un meilleur exemple. Cependant, les métaclasses utilisées en interne sont également des exemples de réinterprétation similaire d'attributs particuliers pour déclarer une signification.
Brian
2
Désolé, l'un de mes conmments s'est perdu dans le scénario de temporisation SO. Je trouve que les cours déclaratifs sont presque une abomination. Je sais que les gens adorent ça, et c'est un comportement accepté. Mais (par expérience) je sais que c'est inutilisable dans une situation où vous voulez déclarer des choses UN. La désinscription d'une classe est difficile .
Ali Afshar
17

Commençons par la citation classique de Tim Peter:

Les métaclasses sont une magie plus profonde que 99% des utilisateurs ne devraient jamais craindre. Si vous vous demandez si vous en avez besoin, vous n'en avez pas besoin (les personnes qui en ont réellement besoin savent avec certitude qu'elles en ont besoin et n'ont pas besoin d'expliquer pourquoi). Tim Peters (clp après 2002-12-22)

Cela dit, j'ai (périodiquement) rencontré de véritables utilisations de métaclasses. Celui qui me vient à l'esprit est 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 se produit par le biais de métaclasses. Il crée toutes sortes de classes d'exception, de classes de gestionnaire, etc. etc.

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

Peter Rowell
la source
Eh bien, oui, je vois le point. Je ne me demande pas "comment" ou "pourquoi" utiliser des métaclasses, je me demande le "qui" et le "quoi". Les ORM sont un cas courant ici, je vois. Malheureusement, l'ORM de Django est assez médiocre par rapport à SQLAlchemy qui a moins de magie. La magie est mauvaise, et les métaclasses ne sont vraiment pas nécessaires pour cela.
Ali Afshar
9
Après avoir lu la citation de Tim Peters dans le passé, le temps a montré que sa déclaration est plutôt inutile. Ce n'est qu'après avoir recherché des métaclasses Python ici sur StackOverflow qu'il est devenu évident de même les implémenter. Après m'être forcé à apprendre à écrire et à utiliser des métaclasses, leurs capacités m'ont étonné et m'ont donné une bien meilleure compréhension du fonctionnement de Python. Les classes peuvent fournir du code réutilisable et les métaclasses peuvent fournir des améliorations réutilisables pour ces classes.
Noctis Skytower
6

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 tiré d' une métaclasse conservatrice par Ian Bicking:

Les métaclasses que j'ai utilisées ont été principalement destinées à supporter 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 un DSL en Python (pdf).

Edit (par Ali): Un exemple de 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 toute raison d'utiliser des métaclasses. En outre, il convient de noter que votre exemple utilise un mélange de classes et d'instances, ce qui indique sûrement que vous ne pouvez pas tout faire avec des métaclasses. Et crée une manière 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.

jfs
la source
Merci pour cela. C'est un très bon exemple de cas d'utilisation que je pense inutile, laid et ingérable, qui serait plus simple basé sur une simple instance de collection (avec des collections imbriquées si nécessaire).
Ali Afshar
1
@Ali A: vous êtes invités à fournir un exemple concret de comparaison côte à côte entre la syntaxe déclarative via des métaclasses et une approche basée sur une instance de collection simple.
jfs
@Ali A: vous pouvez modifier ma réponse sur place pour ajouter un exemple de style de collection.
jfs
Ok fait ça. Désolé, je suis un peu pressé aujourd'hui, mais j'essaierai de répondre à vos questions plus tard / demain. Joyeuses fêtes!
Ali Afshar
2
Le deuxième exemple est moche car vous avez dû lier l'instance du validateur à son nom. Une façon légèrement meilleure de le faire est d'utiliser un dictionnaire au lieu d'une liste, mais alors, en python, les classes ne sont que du sucre de syntaxe pour le dictionnaire, alors pourquoi ne pas utiliser des classes? Vous obtenez également une validation de nom gratuite car les babes python ne peuvent pas contenir d'espaces ou de caractères spéciaux qu'une chaîne pourrait contenir.
Lie Ryan
6

Un modèle raisonnable d'utilisation de métaclasse consiste à faire quelque chose une fois lorsqu'une classe est définie plutôt que de manière répétée chaque fois que la même classe est instanciée.

Lorsque plusieurs classes partagent le même comportement spécial, la répétition __metaclass__=Xest évidemment meilleure que la répétition du code à usage spécial et / ou l'introduction de 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 sont 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 defet classdans le corps de la définition de classe.

utilisateur412090
la source
5

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

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

# 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é toute l'API Python Flickr à partir du site Web, je ne connais vraiment pas les définitions de classe au moment de l'exécution. Être capable de générer dynamiquement des types était très utile.

Triptyque
la source
2
Vous pouvez générer dynamiquement des types sans utiliser de métaclasses. >>> aide (type)
Ali Afshar
8
Même si vous n'êtes pas au courant, vous êtes en utilisant métaclasses alors. type est une métaclasse, en fait la plus courante. :-)
Veky
5

Je pensais la même chose hier et tout à fait d'accord. Les complications dans le 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 pythonique à mon avis. Cela nécessite aussi normalement beaucoup de copy.copy () ing (pour maintenir l'héritage et pour copier de classe en instance) et signifie que vous devez regarder dans de nombreux endroits pour voir ce qui se passe (toujours à la recherche de la métaclasse), ce qui va à l'encontre du grain de python également. J'ai parcouru 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 doit être laissé aux descripteurs (tels que les propriétés et les méthodes) et aux données immuables. Ruby a un meilleur support pour de tels styles déclaratifs et je suis heureux que le langage Python de base ne suit pas cette voie.

Je peux voir leur utilisation pour le débogage, ajouter une métaclasse à toutes vos classes de base pour obtenir des informations plus riches. Je vois aussi leur utilisation uniquement dans de (très) grands projets pour se débarrasser de certains codes standard (mais à perte de clarté). sqlalchemy, par 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 "hello" (disons une méthode qui a ajouté "hello" à la fin d'une chaîne). Il pourrait être bon pour la maintenabilité de vous assurer que vous n'avez pas à é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.

Le besoin de ceci est cependant si rare et ne réduit qu'un peu la saisie que cela ne vaut pas vraiment la peine d'être pris en compte à moins que vous n'ayez une base de code suffisamment grande.

David Raznick
la source
5

Tu n'as 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, il peut être très pratique dans Smalltalk et Ruby de pouvoir modifier une classe existante, mais Python n'aime pas le faire directement.

Il existe un excellent article DeveloperWorks sur la métaclasse en Python qui pourrait vous aider. L' article Wikipédia est également très bon.

Charlie Martin
la source
1
Vous n'avez pas non plus besoin d'objets pour faire de la programmation orientée objet - vous pouvez le faire avec des fonctions de première classe. Vous n'avez donc pas besoin d'utiliser des objets. Mais ils sont là pour plus de commodité. Je ne suis donc pas sûr du point que vous essayez de faire valoir dans le premier paragraphe.
Tyler Crompton
1
Revenez à la question.
Charlie Martin
4

Certaines bibliothèques d'interface graphique ont des problèmes lorsque plusieurs threads tentent d'interagir avec eux. tkinteren est un exemple; et bien que l'on puisse traiter 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.

Être capable 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 fait cela à l'aide d'une métaclasse fournie par la threadbox module - les événements et les files d'attente ne sont pas nécessaires.

Un aspect threadboxintéressant 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 des métaclasses est qu'elles s'exécutent également sur des classes héritées. Des programmes qui s’écrivent eux-mêmes - pourquoi pas?

Noctis Skytower
la source
4

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 à fouiller avec les vôtres, ajoutez un ou deux niveaux supplémentaires pour les empêcher d'entrer. 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 faire. Django est un exemple parfait)

Mike A
la source
7
Je ne suis pas sûr que la motivation était la même à Django.
Ali Afshar
3

Les métaclasses ne remplacent pas la programmation! Ce n'est qu'une astuce qui permet d'automatiser ou de rendre certaines tâches plus élégantes. La bibliothèque de coloration syntaxique Pygments en est un bon exemple . Il a une classe appelée RegexLexerqui 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.

Benjamin Peterson
la source
Eh bien, à mon avis, cette affaire Pygments est tout simplement inutile. Pourquoi ne pas avoir une collection simple comme un dict, pourquoi forcer une classe à faire cela?
Ali Afshar
4
Parce qu'une classe nice encapule l'idée de Lexer et a d'autres méthodes utiles comme guess_filename (), etc.
Benjamin Peterson
3

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.

hyperboréen
la source
5
Oui, cela fonctionne. Vous pouvez également utiliser une superclasse, qui est au moins explicite et suivable dans le code. Par intérêt, pourquoi avez-vous utilisé cela?
Ali Afshar
2

C'est une utilisation mineure, mais ... une chose pour laquelle j'ai trouvé les métaclasses utiles est d'appeler une fonction chaque fois qu'une sous-classe est créée. J'ai codifié cela dans une métaclasse qui recherche un __initsubclass__attribut: chaque fois qu'une sous-classe est créée, toutes les classes parentes qui définissent cette méthode sont invoqué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 des fonctions ou à créer des métaclasses personnalisées qui effectuer 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 c'est inattendu si vous regardez une définition de classe hors de son contexte ... et je me suis donc éloigné de l'utilisation de cette solution pour tout ce qui est sérieux en plus initialisation d'un __superattribut pour chaque classe et instance.

Eli Collins
la source
1

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 du recensement américain de http://census.ire.org/data/bulkdata.html

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

Je voulais a) pouvoir accéder à ces colonnes en utilisant une model_instance.p012017syntaxe, 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-classé SQLAlchemy's DeclarativeMetapour itérer à travers une plage de les colonnes et créer automatiquement des 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 ensuite utiliser cette métaclasse pour ma définition de modèle et accéder aux champs automatiquement énumérés sur le modèle:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

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

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

    ...
Geoffrey Hing
la source
1

Il semble y avoir une utilisation légitime décrite ici - Réécrire des chaînes de documents Python avec une métaclasse.

Mistermarko
la source
0

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 ordonnés de la manière dont ils avaient été déclarés pour en construire le format filaire 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 pour les métaclasses:

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

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

GeeF
la source
0

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
  • toute classe héritée du type est une métaclasse
  • la métaclasse pourrait contrôler la création de classe
  • la métaclasse pourrait également contrôler la création de la métaclasse (elle pourrait donc 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, type create type et type create instance
imbécile
la source
0

Un autre cas d'utilisation est celui où vous souhaitez pouvoir modifier les attributs au niveau de la classe et être sûr que cela n'affecte que l'objet en question. En pratique, cela implique de "fusionner" les phases des métaclasses et des instanciations de classes, ce qui vous amène à ne traiter que des instances de classe de leur propre type (unique).

Je devais aussi le faire lorsque (pour des raisons de lisibilité et de polymorphisme ) nous voulions définir dynamiquement des property valeurs renvoyées (pouvant) résulter de calculs basés sur des attributs au niveau de l'instance (souvent changeants), ce qui ne peut être fait qu'au niveau de la classe , c'est- à- dire après l'instanciation de la métaclasse et avant l'instanciation de la classe.

rester en vie
la source