8
votes

Expérience de l'opérateur d'interception sur la métaclasse

J'ai une classe qui doit faire une magie avec chaque opérateur, comme __ ajouter __ code>, __ sous __ code> et ainsi de suite.

au lieu de créer chaque fonction dans la classe , J'ai une métaclasse qui définit chaque opérateur dans le module de l'opérateur. P>

>>> f = FuncBuilder()
>>> g = f ** 2
>>> g(10)
100
>>> g
<var [('pow', 2)]>


4 commentaires

"J'ai une classe qui doit faire de la magie avec chaque opérateur" - pourquoi? On dirait que vous aboyer un arbre très compliqué ...


@Lennartregebro J'écris un générateur de fonctions utilisant les opérateurs sur certains objets. F = FuncBuilder (); g = f ** 2 + 1; g (10) == 101 . Ce n'est pas quelque chose de très utile (beaucoup d'appels de fonction), mais est un peu amusant à utiliser: D


@Lennartregebro J'ai posté le code complet.


Ok, vous avez donc créé un moyen de faire de Lambdas. :-) Tant que vous le faites pour le plaisir, tout va bien. :-)


4 Réponses :


1
votes

On dirait que vous faites des choses trop compliquées. Vous pouvez définir une classe de mixine et hériter de celui-ci. Ceci est à la fois plus simple que d'utiliser des métaclasses et fonctionnera plus rapidement que d'utiliser __ getattr __ . xxx

alors chaque classe que vous souhaitez avoir ces opérateurs, hériter de l'operatortimixin. xxx

générant les méthodes de l'opérateur lorsqu'il est nécessaire n'est pas une bonne idée: __ getattr __ est lent par rapport à la recherche de méthode ordinaire, et puisque les méthodes sont stockées. Une fois (sur la classe Mixin), il ne fait presque rien.


7 commentaires

Oui, il y a au moins 10 opérateurs (plus des formulaires surveillé) et je ne veux pas les écrire à la main et appeler exactement la même fonction (changer l'opérateur) pour chacun d'eux.


Mon idée est maintenant de seulement créer le Func ou rfuncon lorsque l'opérateur est appelé.


Qu'est-ce qui créera les fonctions qui vous donneront paresseusement?


C'est parce que la classe est un générateur de fonction. Il est conçu pour être utilisé peu de fois et l'objet résultant est celui qui est appelé autant de fois que l'utilisateur veut.


Dans ce cas, je ferais une classe pleine de fonctions telles que "Make_adder", etc. N'abandonnez rien. Ensuite, sous-classe de cette baseClass pour chaque classe spécifique dont vous avez besoin.


Cela enlèverait le plaisir d'utiliser des opérateurs;)


__ getattr __ n'est même pas appelé pour __ xxx __ méthodes. Donc, oui, performance moche. ;)



7
votes

Quelques magies noires Vous permet de réaliser votre objectif: xxx

sortie xxx

maintenant que vous pouvez utiliser Votre deuxième échantillon de code littéralement en héritant de mon classe de base classe de base. Vous obtenez même un avantage supplémentaire: __ getattr __ sera appelé une seule fois par instance et opérateur et il n'y a pas de couche supplémentaire de récursivité impliquée pour la mise en cache. Nous contourgons par la présente le problème des appels de méthode lentement par rapport à la recherche de méthode (comme Paul Hankin remarqua correctement).

NOTE : La boucle pour ajouter les méthodes de l'opérateur n'est exécutée qu'une fois dans Votre programme entier, la préparation prend des frais généraux constants dans la gamme de millisecondes.


10 commentaires

Eh bien, on dirait que votre pour La boucle ajoute tous les opérateurs à la classe (regardez mon code, je le fais aussi). Je veux pas les avoir :). BTW, je pense que c'est déjà une amélioration.


@Jbernardo: regarde à nouveau. Cela fonctionne entièrement différent de votre code. Ce qui est ajouté ne sont pas les fonctions de l'opérateur créées, mais uniquement des emballages peu profonds autour d'un __ getattr __ appel. Cela est nécessaire, car, comme vous l'avez dit, vous ne pouvez pas intercepter ces appels de méthode à l'aide d'une fonction personnalisée __ getattr __ . Comme la boucle n'est exécutée que une fois dans votre programme entier et que le nombre d'opérateurs est fini, il prend des frais généraux constants dans la gamme de millisecondes. Fondamentalement, il s'agit d'un hack pour vous permettre d'utiliser __ getattr __ pour intercepter les opérateurs tels que toutes les autres méthodes (ce que vous avez demandé exactement).


Je comprends votre code (vous devez également ajouter ces commentaires à la réponse), mais ce que vous faites est: x + y -> x .__ add__ -> x .__ getattr __ ('__ ajouter __') . C'est une idée intéressante, mais semble ne pas avoir que les opérateurs sont impossibles d'une manière ou d'une autre.


C'est plus comme x + y -> x .__ add__ -> cached_func = x .__ getattr __ ('__ ajouter __') . La deuxième fois que c'est x + y -> cached_func directement. Vous avez raison dans la mesure où il n'est pas possible d'intercepter l'addition sans avoir un __ ajouter __ méthode (pourquoi devrait-il être?). Cela devrait être le plus proche que vous puissiez accéder à une solution de votre problème.


J'accepte votre réponse car est le plus proche que je puisse obtenir de ce que je veux, mais j'attendrai un peu plus pour donner la prime. Merci


Ok, assez juste. Peut-être que quelqu'un propose une solution plus élégante :)


@Éthanfurman: Je suis un codeur rubis, alors je dois aimer les métaclasses :)


Tout comme un point de clarification, vous n'utilisez pas réellement de métaclasses au sens Python.


@Éthanfurman: Je sais, c'est plus une simulation de singleton / d'Eigen / Métaclasses à Ruby.


@ Niklasb.justez une petite question - pourquoi créeriez-vous la classe temporaire d'instance? Cela fonctionnera parfaitement sans cela et compte tenu du fait que ses méthodes vont utiliser des variables d'instance et non des variables pré-définies, alors elles fonctionnent parfaitement dans n'importe quel autre exemple de la même classe.



3
votes

Le problème à la main est que Python recherche __ xxx __ sur la classe de l'objet, non sur l'objet lui-même - et s'il n'est pas trouvé, il ne retombe pas à __ getattr__ ni __ getattribute __ .

Le seul moyen d'intercepter de tels appels est d'avoir une méthode déjà là. Il peut s'agir d'une fonction de talon, comme dans la réponse de Niklas Baumstark, ou il peut s'agir de la fonction de remplacement à part entière; de toute façon, cependant, là doit être quelque chose déjà là ou que vous ne pourrez pas intercepter de tels appels.

Si vous lisez de près, vous aurez remarqué que votre exigence pour que la méthode finale soit liée à l'instance n'est pas une solution possible - vous pouvez le faire, mais Python ne l'appellera jamais comme Python regarde le Classe de l'instance, pas l'instance, pour __ xxx __ méthodes. La solution de Niklas Baumstark de la fabrication d'une classe Temp unique pour chaque instance est aussi proche que possible de l'exigence.


0 commentaires

0
votes

Si vous souhaitez atteindre votre objectif sans métaclasses, vous pouvez ajouter ce qui suit à votre code:

>>> l = ShowMeList(range(8))
>>> len(l)
Wrapping
8
>>> l
Wrapping 
[0, 1, 2, 3, 4, 5, 6, 7]
>>> print(l)
Wrapping
[0, 1, 2, 3, 4, 5, 6, 7]


0 commentaires