2012-03-30 23 views
23

Escribí un contenedor que tiene otro objeto como atributo. Este contenedor deriva (reenvía) todas las solicitudes de atributos con __getattr__ y __setattr__ al objeto almacenado como el atributo. ¿Qué más debo proporcionar para mi proxy para que el contenedor se vea como la clase envuelta en circunstancias normales?Cómo falsificar/proxy una clase en Python

supongo que tengo que arreglar las cosas como herencia, tal vez __repr__, ... ¿Qué más necesito para cuidar y ¿cómo lo arreglo de herencia por lo que instanceof() obras?

EDIT: Mi intento de hacer un proxy función, sin embargo, como no entiendo la receta completamente, se produce un error :(

setattr_=object.__setattr__ 
getattr_=object.__getattribute__ 

class Proxy(object): 
    __slots__=["_func", "_params", "_kwargs", "_obj", "_loaded", "__weakref__"] 
    def __init__(self, func, *params, **kwargs): 
     setattr_(self, "_func", func) 
     setattr_(self, "_params", params) 
     setattr_(self, "_kwargs", kwargs) 

     setattr_(self, "_obj", None) 
     setattr_(self, "_loaded", False) 

    def _get_obj(self): 
     if getattr_(self, "_loaded")==False: 
      print("Loading") 
      setattr_(self, "_obj", getattr_(self, "_func")(*getattr_(self, "_params"), **getattr_(self, "_kwargs"))) 
      setattr_(self, "_loaded", True) 

     return getattr_(self, "_obj") 
    # 
    # proxying (special cases) 
    # 
    def __getattribute__(self, name): 
     return getattr(getattr_(self, "_get_obj")(), name) 
    def __delattr__(self, name): 
     delattr(getattr_(self, "_get_obj")(), name) 
    def __setattr__(self, name, value): 
     setattr(getattr_(self, "_get_obj")(), name, value) 

    def __nonzero__(self): 
     return bool(getattr_(self, "_get_obj")()) 
    def __str__(self): 
     return str(getattr_(self, "_get_obj")()) 
    def __repr__(self): 
     return repr(getattr_(self, "_get_obj")()) 

    # 
    # factories 
    # 
    _special_names=[ 
     '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__', 
     '__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__', 
     '__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__', 
     '__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__', 
     '__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__', 
     '__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__', 
     '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', 
     '__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', 
     '__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', 
     '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', 
     '__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__', 
     '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', 
     '__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__', 
     '__truediv__', '__xor__', 'next', 
    ] 

    @classmethod 
    def _create_class_proxy(cls, theclass): 
     """creates a proxy for the given class""" 

     def make_method(name): 
      def method(self, *args, **kw): 
       return getattr(getattr_(self, "_get_obj")(), name)(*args, **kw) 
      return method 

     namespace={} 
     for name in cls._special_names: 
      if hasattr(theclass, name): 
       namespace[name]=make_method(name) 
     return type("%s(%s)"%(cls.__name__, theclass.__name__), (cls,), namespace) 

    def __new__(cls, obj, *args, **kwargs): 
     """ 
     creates an proxy instance referencing `obj`. (obj, *args, **kwargs) are 
     passed to this class' __init__, so deriving classes can define an 
     __init__ method of their own. 
     note: _class_proxy_cache is unique per deriving class (each deriving 
     class must hold its own cache) 
     """ 
     try: 
      cache=cls.__dict__["_class_proxy_cache"] 
     except KeyError: 
      cls._class_proxy_cache=cache={} 
     try: 
      theclass=cache[obj.__class__] 
     except KeyError: 
      cache[obj.__class__]=theclass=cls._create_class_proxy(obj.__class__) 
     ins=object.__new__(theclass) 
     theclass.__init__(ins, obj, *args, **kwargs) 
     return ins 

if __name__=='__main__': 
    def t(x, y): 
     print("Running t") 
     return x+y 

    a=Proxy(t, "a", "b") 
    print("Go") 
    print(a+"c") 
+1

Fuera de interés, ¿por qué no heredas? – brice

+1

Porque se supone que esta envoltura envuelve todo tipo de objetos. Es un proxy general. – Gerenuk

+0

Has cambiado la receta un poco. Parece que lo que realmente quieres es 'functools.partial', pero tal vez no; ¿Cuál es el problema que realmente estás tratando de resolver? – SingleNegationElimination

Respuesta

20

Este problema está razonablemente bien abordado por esta receta:

Object Proxying (Python recipe)

La idea general que tiene que seguir es que se accede a la mayoría de los métodos de clases a través de una combinación de __getattr__ y __getattribute__ ya sea en la clase misma o en su metaclase, pero esto no se aplica a los métodos especiales de python, los que comienzan y terminan con doble guión bajo, para que se encuentren, deben ser métodos reales en la clase real, no es posible el proxy de atributos.

Cuál de estos métodos debe proporcionar dependerá obviamente de cuál de esos métodos ofrece la clase proxied. Los métodos especiales que debe proporcionar para isinstance() son los métodos __instancecheck__ and __subclasscheck__. para que repr() funcione, también debe definir un __repr__() apropiado en la propia clase de proxy.

+0

Parece muy interesante, sin embargo, me resulta muy difícil comprender todos los detalles :(Intenté convertirlo en un proxy para las llamadas a funciones, ver edición. Sin embargo, supongo que necesito para hacer la generación de nombres especiales de alguna manera después de que se inicialice el objeto. ¿Puede ayudarme a entender lo que necesito cambiar? – Gerenuk

3

En general, se puede usar la biblioteca wrapt (pypi), que hace el trabajo pesado para usted:

El módulo wrapt se centra en gran medida de corrección. Por lo tanto, va más allá de los mecanismos existentes, como functools.wraps() para garantizar que los decoradores conservan la introspectabilidad, las firmas, las capacidades de verificación de tipos, etc. [...]

Para garantizar que la sobrecarga sea lo más mínima posible, una C módulo de extensión se utiliza para componentes de rendimiento crítico

Es supports creating custom wrapper classes. Para agregar sus propios atributos, debe declararlos de tal manera que wrapt no intente pasarlos a la instancia envuelta. Puede:

  • prefijo del atributo con _self_ y añadir propiedades para el acceso
  • Declarar el atributo a nivel de clase, además de en __init__
  • Use espacios, si es apropiado para su clase (no mencionado en la documentación), así:

    class ExtendedMesh(ObjectProxy): 
        __slots__ = ('foo') 
    
        def __init__(self, subject): 
         super().__init__(subject) 
         self.foo = "bar" 
    

también supports function wrappers, que migh se adapta a tu propósito

Cuestiones relacionadas