Émulation de l'appartenance-test en Python: délégation correcte de __contains__ à l'objet-contenu
-
06-07-2019 - |
Question
Je suis habitué à ce que Python permette à certaines astuces de déléguer des fonctionnalités à d'autres objets. La délégation aux objets contenus en est un exemple.
Mais il me semble que je n'ai pas de chance quand je veux déléguer __contains __:
class A(object):
def __init__(self):
self.mydict = {}
self.__contains__ = self.mydict.__contains__
a = A()
1 in a
je reçois:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument of type 'A' is not iterable
Qu'est-ce que je fais mal? Quand j'appelle un .__ contient __ (1), tout se passe bien. J'ai même essayé de définir une méthode __iter __ dans A pour que A ressemble davantage à une variable, mais cela n'a pas aidé. Qu'est-ce qui me manque ici?
La solution
Les méthodes spéciales telles que __ contient __
ne sont spéciales que lorsqu'elles sont définies sur la classe, pas sur l'instance (sauf dans les classes héritées de Python 2, que vous ne devriez pas utiliser quand même » ).
Alors, votre délégation au niveau de la classe:
class A(object):
def __init__(self):
self.mydict = {}
def __contains__(self, other):
return self.mydict.__contains__(other)
Je préférerais réellement épeler ce dernier sous la forme en renvoyer un autre dans self.mydict
, mais il s'agit d'un problème de style mineur.
Modifier : si et quand "redirige totalement dynamique par instance des méthodes spéciales" (comme les anciennes classes de style offertes) est indispensable, il n'est pas difficile de l'implémenter avec des classes de nouveau style: vous avez juste besoin que chaque instance qui a un tel besoin particulier soit encapsulée dans sa propre classe spéciale. Par exemple:
class BlackMagic(object):
def __init__(self):
self.mydict = {}
self.__class__ = type(self.__class__.__name__, (self.__class__,), {})
self.__class__.__contains__ = self.mydict.__contains__
Essentiellement, après le peu de magie noire qui a réaffecté self .__ class __
à un nouvel objet de classe (qui se comporte exactement comme le précédent mais dont le dict est vide et qui ne contient aucune autre instance sauf celle-ci self
), n'importe où dans une classe de style ancien que vous affecteriez à self .__ nom magique __
, attribuez-le à self .__ classe __.__ nom magique __
à la place (et assurez-vous qu'il une méthode intégrée ou staticmethod
, pas une fonction Python normale, à moins bien sûr que dans un cas différent, vous souhaitiez qu'elle reçoive le self
lorsqu'elle est appelée sur l'instance).
Incidemment, l’opérateur dans
sur une instance de cette classe BlackMagic
est plus rapide que ce n’est le cas avec aucune des fonctions précédemment proposées. solutions - ou du moins si je mesure avec mon -mtimeit
habituel (allant directement à la méthode intégrée
, au lieu de suivre les routes de recherche normales impliquant l'héritage et descripteurs, réduit un peu les frais généraux).
Une métaclasse pour automatiser l'idée de self .__ class __
ne serait pas difficile à écrire (elle pourrait effectuer le sale travail de la méthode __ new __
de la classe générée, et peut-être aussi définir tous les noms magiques à assigner réellement à la classe si assigné à l'instance, soit via __ setattr __
, soit par plusieurs propriétés many ). Mais cela ne serait justifié que si le besoin de cette fonctionnalité était vraiment répandu (par exemple, le portage d’un énorme projet ancien de Python 1.5.2 qui utilisait généreusement des "méthodes spéciales par instance" pour Python moderne, y compris Python 3).
Est-ce que je recommande "astucieux"? ou "magie noire" solutions? Non, je ne le fais pas: il est presque toujours préférable de faire les choses de manière simple et directe. Mais " presque " est un mot important ici, et il est agréable d’avoir sous la main de tels "crochets" avancés dans les rares cas, mais non inexistants, où leur utilisation peut réellement être justifiée.