2011-07-23 2 views
6

J'ai récemment développé une classe nommée DocumentWrapper autour d'un objet de document ORM en Python pour y ajouter de manière transparente quelques fonctionnalités sans modifier son interface de quelque façon que ce soit.Comment fausser le type avec Python

J'ai juste un problème avec ceci. Disons que j'ai un objet User enveloppé dedans. L'appel isinstance(some_var, User) renverra False car some_var est en effet une instance de DocumentWrapper.

Existe-t-il un moyen de truquer le type d'un objet en Python pour avoir le même appel retour True?

+0

héritage multiple? – JBernardo

+1

'isinstance (some_var.user, Utilisateur)'? Qu'essayez-vous de faire? –

+0

J'essaie juste d'avoir un wrapper transparent, qui se comporte exactement comme la classe enveloppée. Y compris avec isinstance. L'héritage multiple n'est pas la solution, au moins parce que l'utilisateur est l'une des nombreuses classes que DocumentWrapper encapsule. (Je n'ai pas le contrôle sur ces classes, je ne suis pas capable de changer leur arbre d'héritage.) – Pierre

Répondre

7

Test du typed'un objet est généralement un antipattern en python. Dans certains cas, il est logique de tester le type « canard » de l'objet, quelque chose comme:

hasattr(some_var, "username") 

Mais même cela est indésirable, par exemple, il y a des raisons pour lesquelles cette expression pourrait revenir faux, même si une enveloppe utilise un peu de magie avec __getattribute__ pour attribuer correctement l'attribut.

Il est généralement préférable de ne permettre aux variables de prendre qu'un seul type abstrait, et éventuellement None. Différents comportements basés sur différentes entrées doivent être atteints en transmettant les données éventuellement typées dans différentes variables.Vous voulez faire quelque chose comme ceci:

def dosomething(some_user=None, some_otherthing=None): 
    if some_user is not None: 
     #do the "User" type action 
    elif some_otherthing is not None: 
     #etc... 
    else: 
     raise ValueError("not enough arguments") 

Bien sûr, tout cela suppose que vous avez un certain niveau de contrôle du code qui fait la vérification de type. Supposons que ce ne soit pas. pour que "isinstance()" renvoie true, la classe doit apparaître dans les bases de l'instance ou la classe doit avoir un __instancecheck__. Puisque vous ne contrôlez aucune de ces choses pour la classe, vous devez recourir à des manigances sur l'instance. Faites quelque chose comme ceci:

def wrap_user(instance): 
    class wrapped_user(type(instance)): 
     __metaclass__ = type 
     def __new__(cls): 
      pass 
     def __init__(self): 
      pass 
     def __getattribute__(self, attr): 
      self_dict = object.__getattribute__(type(self), '__dict__') 
      if attr in self_dict: 
       return self_dict[attr] 
      return getattr(instance, attr) 
     def extra_feature(self, foo): 
      return instance.username + foo # or whatever 
    return wrapped_user() 

Ce que nous faisons est la création d'une nouvelle classe dynamique au moment où nous devons envelopper l'instance, et d'hériter en fait de l'objet de __class__ enveloppé. Nous allons aussi au problème supplémentaire de surcharger le __metaclass__, au cas où l'original aurait des comportements supplémentaires que nous ne voulons pas rencontrer (comme chercher une table de base de données avec un certain nom de classe). Une bonne commodité de ce style est que nous ne devons jamais créer d'attributs d'instance sur la classe wrapper, il n'y a pas self.wrapped_object, puisque cette valeur est présente à heure de création de la classe.

Edit: Comme indiqué dans les commentaires, ce qui précède ne fonctionne que pour certains types simples, si vous avez besoin de proxy attributs plus élaborés sur l'objet cible, (par exemple, les méthodes), puis voir la réponse suivante: Python - Faking Type Continued

+0

Merci beaucoup pour votre aide précieuse =) – Pierre

+0

Et pourquoi est-ce que le __metaclass__ = type (apparemment redondant) nécessaire? –

+0

Vous avez neutralisé efficacement le [protocole descripteur] (https://docs.python.org/3/howto/descriptor.html), par ex. 'wrap_user (obj) .extra_feature()' renvoie une méthode non liée. Vous devez, à tout le moins, vérifier les méthodes '__get__' sur les objets que vous récupérez depuis' self_dict'. Voir [Python - Faking Type Suite] (https://stackoverflow.com/q/31658171) pour une question de suivi posée par quelqu'un. –

0

Il semble que vous voulez tester le type de l'objet vos enveloppes DocumentWrapper, pas le type de l'DocumentWrapper lui-même. Si c'est le cas, l'interface à DocumentWrapper doit exposer ce type. Vous pouvez ajouter une méthode à votre classe DocumentWrapper qui renvoie le type de l'objet enveloppé, par exemple. Mais je ne pense pas que rendre l'appel à isinstance ambigu, en le faisant retourner Vrai quand ce n'est pas le cas, est la bonne façon de résoudre ce problème.

0

La meilleure façon est d'hériter DocumentWrapper de l'utilisateur lui-même, ou mélanger en modèle et en faisant plusieurs inherintance de nombreuses classes

class DocumentWrapper(User, object) 

Vous pouvez également faux isinstance() résultats en manipulant obj.__class__ mais cela est profond niveau de magie et ne devrait pas être fait.

+0

Merci. L'utilisateur n'est pas le seul type de document enveloppé, donc cela ne fonctionnera malheureusement pas. Mais merci, je ne savais même pas que l'héritage multiple était possible avec Python =) – Pierre

11

Vous pouvez utiliser la méthode magique __instancecheck__ pour remplacer le comportement par défaut isinstance:

@classmethod 
def __instancecheck__(cls, instance): 
    return isinstance(instance, User) 

C'est que si vous voulez que votre objet soit une enveloppe transparente; c'est-à-dire, si vous voulez un DocumentWrapper se comporter comme un User. Sinon, exposez simplement la classe enveloppée en tant qu'attribut.

Ceci est une addition de Python 3; il est venu avec des classes de base abstraites. Vous ne pouvez pas faire la même chose en Python 2.

+1

J'ai entendu ça, merci ... Mais j'utilise Python 2.x + ( – Pierre

+2

c'est dans 2.6 https: //docs.python. org/2/reference/datamodel.html # customizing-instance-et-subclass-checks – Anentropic

+4

Grande mise en garde, cette méthode va dans la classe ** meta ** ** de la classe ** wrapped ** –

1

Voici une solution en utilisant métaclasse, mais vous devez modifier les classes enveloppées:

>>> class DocumentWrapper: 
    def __init__(self, wrapped_obj): 
     self.wrapped_obj = wrapped_obj 

>>> class MetaWrapper(abc.ABCMeta): 
    def __instancecheck__(self, instance): 
     try: 
      return isinstance(instance.wrapped_obj, self) 
     except: 
      return isinstance(instance, self) 

>>> class User(metaclass=MetaWrapper): 
    pass 

>>> user=DocumentWrapper(User()) 
>>> isinstance(user,User) 
True 
>>> class User2: 
    pass 

>>> user2=DocumentWrapper(User2()) 
>>> isinstance(user2,User2) 
False 
+0

Grande ampoule, ** vous besoin de modifier les classes enveloppées ** .Vous ne pouvez donc que "simuler" vos propres types, par exemple, pas les chaînes ou les ints –

4

Remplacer __class__ dans votre classe wrapper DocumentWrapper:

class DocumentWrapper(object): 

    @property 
    def __class__(self): 
    return User 

>>> isinstance(DocumentWrapper(), User) 
True 

de cette façon, aucune modification à la classe enveloppé User sont nécessaires.

Python Mock fait la même chose (voir mock.py: 612 dans mock-2.0.0, n'a pas pu trouver les sources en ligne pour lier, désolé).

Questions connexes