2011-07-23 14 views
6

Recientemente desarrollé una clase llamada DocumentWrapper alrededor de un objeto de documento ORM en Python para agregarle algunas funciones sin cambiar su interfaz de ninguna manera.Cómo falsificar el tipo con Python

Solo tengo un problema con esto. Digamos que tengo un objeto User envuelto en él. Llamar isinstance(some_var, User) devolverá False porque some_var de hecho es una instancia de DocumentWrapper.

¿Hay alguna forma de simular el tipo de objeto en Python que tiene la misma devolución de llamada True?

+0

herencia múltiple? – JBernardo

+1

'isinstance (some_var.user, User)'? ¿Qué estás tratando de hacer? –

+0

Solo trato de tener un contenedor transparente, que se comporta * exactamente * como la clase envuelta. Incluyendo con isinstance. La herencia múltiple no es la solución, al menos porque el Usuario es solo una de las muchas clases que envuelve DocumentWrapper. (No tengo control sobre estas clases, no puedo cambiar su árbol de herencia.) – Pierre

Respuesta

7

Prueba de la tipo de un objeto es por lo general un antipattern en python. En algunos casos, tiene sentido para probar el "tipo de pato" del objeto, algo así como:

hasattr(some_var, "username") 

Pero incluso eso es indeseable, por ejemplo, hay razones por las que la expresión puede devolver false, a pesar de que un envoltorio usa algo de magia con __getattribute__ para proxy correcto del atributo.

Por lo general, se prefiere permitir que las variables solo tomen un solo tipo abstracto, y posiblemente None. Se deben lograr diferentes comportamientos basados ​​en diferentes entradas pasando los datos opcionalmente tipados en diferentes variables.¿Quieres hacer algo como esto:

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") 

Por supuesto, todo esto supone que tiene un cierto nivel de control del código que está haciendo la comprobación de tipos. Supongamos que no lo es. para que "isinstance()" devuelva verdadero, la clase debe aparecer en las bases de la instancia, o la clase debe tener un __instancecheck__. Como no controlas ninguna de esas cosas para la clase, tienes que recurrir a algunos chanchullos en la instancia. Hacer algo como esto:

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() 

Lo que estamos haciendo es crear una nueva clase de forma dinámica en el tiempo que necesitamos para envolver la instancia, y de hecho de heredar de __class__ el objeto envuelto. También nos enfrentamos al problema adicional de anular el __metaclass__, en caso de que el original tuviera algunos comportamientos adicionales que realmente no queremos encontrar (como buscar una tabla de base de datos con un cierto nombre de clase). Una buena conveniencia de este estilo es que nunca tenemos que crear ningún atributo de instancia en la clase contenedora, no hay self.wrapped_object, ya que ese valor está presente en tiempo de creación de clase.

Edit: Como se ha señalado en los comentarios, lo anterior sólo funciona para algunos tipos simples, si necesita proxy de atributos más elaboradas en el objeto de destino, (por ejemplo, métodos), a continuación, ver la siguiente respuesta: Python - Faking Type Continued

+0

Muchas gracias por su valiosa ayuda =) – Pierre

+0

¿Y por qué es (aparentemente redundante) '__metaclass__ = tipo' ¿necesario? –

+0

Has castrado eficazmente el [descriptor protocol] (https://docs.python.org/3/howto/descriptor.html), p. 'wrap_user (obj) .extra_feature()' devuelve un método independiente. Necesitas, como mínimo, verificar los métodos '__get__' en los objetos que recuperas de' self_dict'. Ver [Python - Faking Type Continued] (https://stackoverflow.com/q/31658171) para una pregunta de seguimiento que alguien hizo. –

0

Parece que usted quiere probar el tipo del objeto DocumentWrapper sus envolturas, no el tipo de la misma DocumentWrapper. Si eso es correcto, la interfaz para DocumentWrapper necesita exponer ese tipo. Puede agregar un método a su clase DocumentWrapper que devuelve el tipo de objeto envuelto, por ejemplo. Pero no creo que hacer la llamada a isinstance ambigua, haciendo que vuelva True cuando no lo es, es la forma correcta de resolver esto.

0

La mejor manera es heredar DocumentWrapper desde el propio usuario, o mezclar en el patrón y hacer múltiples inherintance de muchas clases

class DocumentWrapper(User, object) 

Puede isinstance también falso() resultados mediante la manipulación de obj.__class__ pero esto es profunda nivel de magia y no debe hacerse.

+0

Gracias. El usuario no es el único tipo de documento envuelto, por lo que no funcionará desafortunadamente. Pero gracias, ni siquiera sabía que la herencia múltiple era posible con Python =) – Pierre

11

Puede utilizar el método mágico __instancecheck__ para anular el comportamiento por defecto isinstance:

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

Esto es sólo si desea que el objeto sea un envoltorio transparente; es decir, si desea que DocumentWrapper se comporte como User. De lo contrario, solo exponga la clase envuelta como un atributo.

Esta es una adición de Python 3; venía con clases base abstractas. No se puede hacer lo mismo en Python 2.

+1

Escuché esto, gracias ... Pero estoy usando Python 2.x + ( – Pierre

+2

esto está en 2.6 https: //docs.python. org/2/reference/datamodel.html # customizing-instance-and-subclass-checks – Anentropic

+4

Gran advertencia, este método va en la clase ** meta ** ** de la clase ** wrapped. –

1

Aquí es una solución mediante el uso de metaclase, pero hay que modificar las clases envueltos:

>>> 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

Bigbulb, ** usted necesita modificar las clases envueltas **. Por lo tanto, solo puede "falsificar" sus propios tipos, por ejemplo, cadenas o enteros. –

4

Invalidar __class__ en su clase contenedora DocumentWrapper:

class DocumentWrapper(object): 

    @property 
    def __class__(self): 
    return User 

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

de esta manera no se necesitan modificaciones a la clase envuelta User.

Python Mock hace lo mismo (ver mock.py:612 en mock-2.0.0, no se pudieron encontrar las fuentes en línea para enlazar, lo siento).

Cuestiones relacionadas