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)
Réponses:
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:
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: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.la source
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:
Inscription (ajout d'une classe à une structure de données):
Chaque fois que vous sous
Model
- classez , votre classe est enregistrée dans lemodels
dictionnaire:Cela peut également être fait avec des décorateurs de classe:
Ou avec une fonction d'enregistrement explicite:
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.
Refactoring (modification des attributs de classe ou ajout de nouveaux):
Chaque fois que vous sous
Model
- classez et définissez desField
attributs, 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 toutes ses classes de base '' attributs à chaque fois):Encore une fois, cela peut être fait (sans héritage) avec un décorateur de classe:
Ou explicitement:
Bien que, contrairement à votre plaidoyer pour une programmation non-méta lisible et maintenable, cela est beaucoup plus encombrant, redondant et sujet aux erreurs:
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.
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 adict
, prenant ainsi en charge les attributs ordonnés, les attributs surchargés et d'autres trucs super sympas: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:
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
None
est une valeur valide pourx
?Voici une façon créative de résoudre le premier problème:
Et voici une façon créative de résoudre le second:
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 implicitement
this
aux 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.
É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 .
la source
__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 ?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.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:
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_foo
et_set_foo
et 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.
la source
Commençons par la citation classique de Tim Peter:
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.
la source
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:
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.
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.
la source
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__=X
est é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 normalesdef
etclass
dans le corps de la définition de classe.la source
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:
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.
la source
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
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.
la source
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.
la source
Certaines bibliothèques d'interface graphique ont des problèmes lorsque plusieurs threads tentent d'interagir avec eux.
tkinter
en 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
threadbox
inté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?la source
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)
la source
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
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.
la source
La façon dont j'ai utilisé les métaclasses était de fournir des attributs aux classes. Prends pour exemple:
mettra l' attribut name sur chaque classe qui aura la métaclasse définie pour pointer vers NameClass.
la source
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
__super
attribut pour chaque classe et instance.la source
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.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-classé SQLAlchemy'sDeclarativeMeta
pour itérer à travers une plage de les colonnes et créer automatiquement des champs de modèle correspondant aux colonnes: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:
la source
Il semble y avoir une utilisation légitime décrite ici - Réécrire des chaînes de documents Python avec une métaclasse.
la source
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.
la source
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:
la source
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.la source