149 votes

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

J'ai un ami qui aime utiliser des métaclasses, et qui 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 me dis que si vous faites quelque chose comme ça à une classe, vous devriez probablement le faire à un objet. Et un petit redesign/refactor est en ordre.

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 désastreux. La programmation doit-elle être remplacée par la métaprogrammation ? L'ajout des décorateurs de classe a malheureusement rendu cette pratique encore plus acceptable.

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

Je vais commencer :

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

(C'est le seul cas auquel je pense, et il n'est pas concret).

3 votes

C'est une excellente question. À en juger par les réponses ci-dessous, il est clair qu'il n'existe pas d'utilisation concrète des métaclasses.

37voto

Brian Points 48423

Le but des métaclasses n'est pas de remplacer la distinction classe/objet par une métaclasse/classe, mais de modifier 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 la déclaration de classe d'une manière qui peut être plus utile pour votre domaine particulier que le comportement 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 type 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 les méthodes appropriées (lecture / obtention de balises, etc.) pour son type. Ajouter 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 ensuite un dictionnaire de {'.mp3' : MP3File, ... } etc., et construit un objet du type approprié lorsque vous demandez un gestionnaire par le biais d'une fonction d'usine.

  • Changement de comportement. Il se peut que vous souhaitiez attribuer une signification particulière à certains attributs, ce qui se traduira par un comportement modifié lorsqu'ils seront présents. Par exemple, vous pouvez rechercher les méthodes dont le nom est _get_foo y _set_foo et les convertir de manière transparente en propriétés. Un exemple concret, voici une recette que j'ai écrite pour donner des définitions de structures plus proches du C. La métaclasse est utilisée pour convertir les éléments déclarés en une chaîne de format struct, en gérant l'héritage etc, et produire une classe capable de la traiter.

    Pour d'autres exemples concrets, jetez un coup d'œil à divers ORM, tels que de sqlalchemy ORM ou sqlobject . Encore une fois, le but est d'interpréter les définitions (ici les définitions des colonnes SQL) avec un sens particulier.

3 votes

Eh bien, oui, le suivi des sous-classes. Mais pourquoi voudriez-vous cela ? Votre exemple est juste implicite pour register_music_file(Mp3File, ['.mp3']), et la manière explicite est plus lisible et maintenable. C'est un exemple des mauvais cas dont je parle.

0 votes

En ce qui concerne l'ORM, parlez-vous de la méthode de définition des tables basée sur les classes, ou des métaclasses sur les objets mappés ? Parce que SQLAlchemy peut (à juste titre) faire correspondre n'importe quelle classe (et je suppose qu'il n'utilise pas de métaclasse pour cette activité).

11 votes

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 - il vaut mieux que tout soit regroupé en un seul endroit.

28voto

Matt Points 377

J'ai une classe qui gère le traçage non interactif, comme un frontal de Matplotlib. Cependant, il arrive que l'on veuille faire des tracés interactifs. Avec seulement quelques fonctions, j'ai trouvé que j'étais capable d'incrémenter le nombre de chiffres, d'appeler le dessin manuellement, etc, mais j'avais besoin de faire cela avant et après chaque appel au 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, plutôt que de faire quelque chose du genre :

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

Cette méthode ne tient pas compte des changements d'API, etc., mais une méthode qui itère sur les attributs de classe dans le fichier __init__ avant de redéfinir les attributs de la classe est plus efficace et permet de garder 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 procéder, mais j'ai trouvé cette méthode efficace. Bien sûr, cela pourrait aussi être fait en __new__ o __init__ mais c'est la solution que j'ai trouvée la plus simple.

20voto

Peter Rowell Points 12444

Commençons par la citation classique de Tim Peter :

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

Cela dit, j'ai (périodiquement) rencontré de véritables utilisations de métaclasses. Celle 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 pour envelopper vos modèles de base de données avec la bonté de l'ORM de Django. Cette magie se produit par le biais de métaclasses. Il crée toutes sortes de classes d'exceptions, de classes de gestionnaires, etc. etc.

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

0 votes

Eh bien, oui, je vois le problème. Je ne me demande pas "comment" ou "pourquoi" utiliser les métaclasses, je me demande "qui" et "quoi". Les ORM sont un cas courant ici, je vois. Malheureusement, l'ORM de Django est assez pauvre comparé à SQLAlchemy qui a moins de magie. La magie est mauvaise, et les métaclasses ne sont vraiment pas nécessaires pour cela.

12 votes

Ayant lu la citation de Tim Peters dans le passé, le temps a montré que sa déclaration est plutôt inutile. Ce n'est que lorsque j'ai fait des recherches sur les métaclasses Python sur StackOverflow que j'ai compris comment les mettre en œuvre. Après m'être forcé à apprendre à écrire et à utiliser les métaclasses, leurs capacités m'ont étonné et m'ont permis de mieux comprendre le fonctionnement réel de Python. Les classes peuvent fournir du code réutilisable, et les métaclasses peuvent fournir des améliorations réutilisables pour ces classes.

7voto

J.F. Sebastian Points 102961

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

Un exemple de base tiré de Une métaclasse conservatrice par Ian Bicking :

Les métaclasses que j'ai utilisées ont été principalement pour supporter une sorte de style déclaratif de programmation. Pour exemple, considérons une validation 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()

D'autres techniques : Ingrédients pour la construction d'un DSL en Python (pdf).

Modifier (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 pouvoir, et éliminent la raison d'utiliser les métaclasses. Il faut aussi noter que votre exemple utilise un mélange de classes et d'instances, ce qui est sûrement une indication que vous ne pouvez pas tout faire avec les 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 plus de magie, plus besoin de métaclasses et une meilleure uniformité.

0 votes

Merci pour cela. C'est un très bon exemple d'un cas d'utilisation que je trouve inutile, laid et ingérable, qui serait plus simple s'il était basé sur une simple instance de collection (avec des collections imbriquées si nécessaire).

1 votes

@Ali A : vous êtes le bienvenu pour fournir un exemple concret de comparaison côte à côte entre la syntaxe déclarative via les métaclasses et une approche basée sur une simple instance de collection.

0 votes

@Ali A : vous pouvez modifier ma réponse en place pour ajouter un exemple de style de collection.

6voto

David Raznick Points 5074

Je pensais la même chose hier encore et je suis tout à fait d'accord. Les complications dans le code causées par les tentatives de le rendre plus déclaratif rendent généralement la base de code plus difficile à maintenir, plus difficile à lire et moins pythique à mon avis. Cela nécessite également 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 regarder à partir de la métaclasse), ce qui va également à l'encontre du grain de python. J'ai parcouru le code de formencode et sqlalchemy pour voir si un tel style déclaratif en valait la peine et il est clair que ce n'est pas le cas. Un tel style devrait être réservé 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 noyau du langage python ne suive pas cette voie.

Je peux voir leur utilité 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 les (très) gros projets pour se débarrasser de certains codes passe-partout (mais au détriment de la clarté). sqlalchemy pour ejemplo 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 génère une méthode dans cette classe avec des propriétés spéciales basées sur "hello" (disons une méthode qui ajoute "hello" à la fin d'une chaîne). Il pourrait être bon pour la maintenabilité de s'assurer que vous n'avez pas à écrire une méthode dans chaque sous-classe que vous faites, au lieu de cela, tout ce que vous avez à définir est method_maker_value.

Le besoin est si rare et ne réduit qu'un peu la saisie que cela ne vaut pas vraiment la peine d'être envisagé à moins d'avoir une base de code assez importante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X