À l'aide de l' __appel__ méthode d'une métaclasse au lieu de __new__?
Lors de la discussion metaclasses, les docs état:
Bien sûr, vous pouvez également remplacer les autres méthodes de la classe (ou en ajouter de nouvelles
méthodes); par exemple la définition d'un personnalisé__call__()
méthode dans l'
métaclasse personnalisé de comportement lorsque la classe est appelée, par exemple, ne pas
toujours la création d'une nouvelle instance.
Mes questions est: supposons que je veux avoir un comportement personnalisé lorsque la classe est appelée, par exemple, la mise en cache au lieu de la création de nouveaux objets. Je peux le faire en remplaçant la __new__
la méthode de la classe. Quand je veux définir une métaclasse avec __call__
à la place? Quelle est cette approche qui n'est pas réalisable avec __new__
?
Vous devez vous connecter pour publier un commentaire.
La réponse directe à votre question est: quand vous voulez faire plus que juste personnaliser la création de l'instance, ou si vous voulez séparer ce que la classe ne de comment il est créé.
Voir ma réponse à La création d'un singleton en Python et les discussions associées.
Il y a plusieurs avantages.
Il vous permet de séparer ce qui le classe ne à partir des détails de comment il est créé. La métaclasse classe et sont chacun responsable d'une chose.
Vous pouvez écrire le code une fois dans une métaclasse, et de l'utiliser pour la personnalisation de plusieurs classes de comportement d'appel, sans se soucier de l'héritage multiple.
Les sous-classes peuvent remplacer les comportements dans leurs
__new__
méthode, mais__call__
sur une métaclasse n'a pas à même appel__new__
à tous.Si il y a travaux d'installation, vous pouvez le faire dans le
__new__
méthode de la métaclasse, et il se produit seulement une fois, au lieu de chaque fois que la classe est appelée.Il y a certainement beaucoup de cas où la personnalisation de
__new__
fonctionne tout aussi bien si vous n'êtes pas inquiet au sujet du principe de responsabilité unique.Mais il y a d'autres cas d'utilisation qui doivent arriver plus tôt, lorsque la classe est créée, plutôt que lorsque l'instance est créée. C'est quand ceux-ci viennent dans de jouer qu'une métaclasse est nécessaire. Voir Quels sont vos (béton) des cas d'utilisation pour les metaclasses en Python? pour beaucoup de grands exemples.
Une différence, c'est que par définition d'une métaclasse
__call__
méthode vous êtes exigeant qu'elle est appelée avant tout de la classe ou sous-classes de la__new__
méthodes ont la possibilité d'être appelé.Avis que
SubFoo.__new__
n'est jamais appelé. En revanche, si vous définissezFoo.__new__
sans une métaclasse, vous permettez à des sous-classes de modifierFoo.__new__
.Bien sûr, vous pouvez définir
MetaFoo.__call__
appelercls.__new__
, mais c'est à vous de voir. En refusant de le faire, vous pouvez empêcher les sous-classes d'avoir leur__new__
méthode appelée.Je ne vois pas l'irrésistible avantage de l'utilisation d'une métaclasse ici. Et depuis "Simple est mieux que complexe", je vous recommande d'utiliser
__new__
.cls.__new__()
seront appelés indirectement si leMetaFoo.__call__()
méthode appellesuper(MetaFoo, cls).__call__(*args, **kwargs)
.class Simple1(object, metaclass = SimpleMeta1):
maintenant... gee merci python-3-les modèles-les expressions idiomatiques-test.readthedocs.io/en/latest/...Les différences subtiles de devenir un peu plus visible lorsque vous observez attentivement l'ordre d'exécution de ces méthodes.
Noter que le code ci-dessus ne fait pas ne rien d'autre que le journal de ce que nous faisons. Chaque méthode diffère à son parent de la mise en œuvre c'est à dire sa valeur par défaut. Donc, à côté de la journalisation en fait, c'est comme si vous aviez simplement déclaré les choses comme suit:
Et maintenant, nous allons créer une instance de
Class_1
Donc si
type
est le parent deMeta_1
on peut imaginer une pseudo mise en œuvre detype.__call__()
en tant que tel:De l'avis de l'ordre d'appel ci-dessus que
Meta_1.__call__()
(ou dans ce castype.__call__()
) est donné la possibilité d'influer sur si oui ou non les appels àClass_1.__new__()
etClass_1.__init__()
sont finalement fait. Au cours de son exécutionMeta_1.__call__()
pourrait renvoyer un objet qui n'a même pas été touché par l'un ou l'autre. Prenez l'exemple de cette approche pour le pattern singleton:Observons ce qui se passe lorsque plusieurs reprises d'essayer de créer un objet de type
Class_2
Maintenant observer cette mise en œuvre à l'aide d'une de la classe de
__new__()
méthode pour essayer de faire la même chose.Avis que cette mise en œuvre, même si avec succès l'enregistrement d'un singleton sur la classe, n'empêche pas
__init__()
d'être appelé, de ce qui se passe implicitement danstype.__call__()
(type
étant la valeur par défaut métaclasse si aucun n'est spécifié). Cela peut conduire à des effets indésirables:Meta_1.__call__
, vous avezrv = super(Meta_1, cls).__call__(*a, **kw)
. Pouvez-vous expliquer pourquoiMeta_1
est le premier argument ensuper
??super
(si pas il y a quelques grandes réponses dans ce site),super
nécessite en premier argument le de la classe dont les parents que vous voulez résoudre, et comme deuxième argument, la exemple de la classe à laquelle vous essayez d'appliquer un parent de la méthode. Icicls
est une instance deMeta_1
, ainsisuper(Meta_1, ...)
signifie que nous voulonsMeta_1
's parents.super(..., cls)
spécifie l'instance qui sera lié à cette méthode lorsqu'elle est appelée (c'est à dire qu'il sera reçu commeself
dans__call__(self, ...)
).super
n'est pas évident. Nous savons déjà queMeta_1
a un parent, qui esttype
. J'aurais pu simplement appliquétype
's la version de__call__
àcls
en faisanttype.__call__(cls, *a, **kw)
.super
est le plus "automatique" par le moyen de demander à un parent de la version de la méthode. Il faut un certain nombre de décisions pour vous. Encore une fois, je vous conseille de la lire (et le descripteur de protocole) pour en savoir plus. La syntaxesuper(class, instance)
est une syntaxe explicite. En Python3, vous pouvez simplement appelersuper()
et il détermine la classe de l'instance et de l'utiliser.super(arg1, arg2)
se regarder dans le MRO de la deuxième argument d'entrée pour trouver le premier argument d'entrée et de retour à la prochaine classe. Maisrv = super(Meta_1, cls).__call__(*a, **kw)
, le MRO pour le 2ème argument(cls
, ouClass_1
), ne contient pas le 1er argument d'entrée(Meta_1
), vous ne pouvez pas trouverMeta_1
dans le MRO pourClass_1
. donc je ne vois pas pourquoi aurait-il fallu pour appelertype.__call__(Class_1)
. C'est pourquoi j'ai demandé.cls
pour trouver l'MRO, mais pas comme on pourrait le penser. Je devine que vous avez pensé qu'il ferait quelque chose d'aussi direct quecls.__mro__
et trouverMeta_1
. Pas tellement, c'estClass_1
's MRO vous êtes à la résoudre en faisant cela, un autre, sans rapport avec les MRO, etMeta_1
n'est pas une partie de celui-ci (Class_1
n' inherit deMeta_1
).cls
même d'avoir une__mro__
propriété est juste un accident à cause d'une classe. Au lieu de cela,super
va chercher le de la classe (une métaclasse dans notre cas) decls
, c'est à direMeta_1
, puis va chercher le MRO à partir de là (c'est à direMeta_1.__mro__
).C'est une question de cycle de vie et ce que vous avez accès.
__call__
est appelée après__new__
et est passé à l'initialisation des paramètres avant ils sont répercutés sur les__init__
, de sorte que vous pouvez les manipuler. Essayez ce code et l'étude de sa sortie:__new__
sur la métaclasse qui se passe quand la de la classe est créé, pas un exemple.__call__
qui se passe quand__new__
arriverait sans la métaclasse.__new__
est liée à la création de l'instance?__new__
, pas la métaclasse est__new__
.__new__
, plutôt que de la métaclasse__new__
.__new__
d'une classe (pas métaclasse) est appelée lorsque l'objet est créé à l'instanciation de la classe. Il est utile si vous souhaitez retourner un objet qui a été créé avant (par exemple un singleton).au lieu de re-création d'un nouvel objet.__new__()
. Parfois, vous pouvez également besoin de la métaclasse__prepare__
méthode d'intervenir plus tôt dans l'instanciation. Ce post c'est une assez bonne vue d'ensemble, bien qu'il pourrait le faire avec un appliqué exemple.J'ai pensé à un étoffé Python 3 version de pyroscope la réponse pourrait être pratique pour quelqu'un de copier, coller et hack avec (probablement moi, quand je me retrouve sur cette page à la recherche de nouveau dans 6 mois). Il est tiré de cet article:
Sorties:
Une autre grande ressource mis en évidence par le même article est David Beazley de PyCon 2013 Python 3 Métaprogrammation tutoriel.