2008-11-03 15 views
6

estoy crear instancias de una clase A (que estoy importando de alguien más, así que no se puede modificar) en mi clase X.Python: envolver las llamadas a métodos con los métodos de pre y post

¿Hay una manera Puedo interceptar o ajustar llamadas a métodos en A? es decir, en el código de abajo puede llamar me

x.a.p1() 

y obtener la salida

X.pre 
A.p1 
X.post 

Muchos TIA!

class A: 
    # in my real application, this is an imported class 
    # that I cannot modify 
    def p1(self): print 'A.p1' 

class X: 
    def __init__(self): 
     self.a=A() 
    def pre(self): print 'X.pre' 
    def post(self): print 'X.post' 

x=X() 
x.a.p1() 

Respuesta

5

Aquí está la solución que yo y mis colegas ocurrió:

from types import MethodType 

class PrePostCaller: 
    def __init__(self, other): 
     self.other = other 

    def pre(self): print 'pre' 
    def post(self): print 'post' 

    def __getattr__(self, name): 
     if hasattr(self.other, name): 
      func = getattr(self.other, name) 
      return lambda *args, **kwargs: self._wrap(func, args, kwargs) 
     raise AttributeError(name) 

    def _wrap(self, func, args, kwargs): 
     self.pre() 
     if type(func) == MethodType: 
      result = func(*args, **kwargs) 
     else: 
      result = func(self.other, *args, **kwargs) 
     self.post() 
     return result 

#Examples of use 
class Foo: 
    def stuff(self): 
     print 'stuff' 

a = PrePostCaller(Foo()) 
a.stuff() 

a = PrePostCaller([1,2,3]) 
print a.count() 

Da:

pre 
stuff 
post 
pre 
post 
0 

Así que cuando se crea una instancia de su objeto, se envuelve con el objeto PrePostCaller . Después de eso, continúa usando el objeto como si fuera una instancia del objeto envuelto. Con esta solución puede hacer el ajuste por instancia.

1

La solución no-silbatos-o-campanas sería escribir una clase contenedora de la clase A que hace precisamente eso.

+0

cierto, pero me gustaría evitar ya que mi programa real incluye muchas más instancias de clase de las que me gustaría envolver. –

+0

Bueno, llama al envoltorio igual, pero ponlo en tu propio espacio de nombres. Funcionará efectivamente como un reemplazo directo, y no es necesario que cambie su código. – Tomalak

1

Se podía modificar la A instancia y reemplazar la función P1 con una función de contenedor:

def wrapped(pre, post, f): 
    def wrapper(*args, **kwargs): 
     pre() 
     retval = f(*args, **kwargs) 
     post() 
     return retval 
    return wrapper 

class Y: 
    def __init__(self): 
     self.a=A() 
     self.a.p1 = wrapped(self.pre, self.post, self.a.p1) 

    def pre(self): print 'X.pre' 
    def post(self): print 'X.post' 
1

He leído recientemente sobre decoradores en pitón, yo no soy la comprensión de ellos todavía, pero parece que pueden ser una solución a tu problema vea la introducción de Bruce Eckel a los decoradores en: http://www.artima.com/weblogs/viewpost.jsp?thread=240808

Tiene algunas más publicaciones sobre el tema allí.

Editar: Tres días más tarde topas con este artículo, que muestra cómo hacer una tarea similar sin decoradores, ¿cuál es el problema con ella y luego se introduce a los decoradores y aplicar una solución bastante completo: http://wordaligned.org/articles/echo

0

Esto es lo Recibí de Steven D'Aprano en comp.lang.python.

# Define two decorator factories. 
def precall(pre): 
    def decorator(f): 
     def newf(*args, **kwargs): 
      pre() 
      return f(*args, **kwargs) 
     return newf 
    return decorator 

def postcall(post): 
    def decorator(f): 
     def newf(*args, **kwargs): 
      x = f(*args, **kwargs) 
      post() 
      return x 
     return newf 
    return decorator 

Ahora puede parche mono clase A si lo desea. Probablemente no sea una gran idea hacer esto en el código de producción, ya que afectará a la clase A en todas partes. [esto está bien para mi aplicación, ya que es básicamente un convertidor de protocolo y hay exactamente una instancia de cada clase está procesando.]

class A: 
    # in my real application, this is an imported class 
    # that I cannot modify 
    def p1(self): print 'A.p1' 

class X: 
    def __init__(self): 
     self.a=A() 
     A.p1 = precall(self.pre)(postcall(self.post)(A.p1)) 
    def pre(self): print 'X.pre' 
    def post(self): print 'X.post' 


x=X() 
x.a.p1() 

da el resultado deseado.

X.pre 
A.p1 
X.post 
+0

En lugar de aplicar parches a la Clase, simplemente aplique el parche Instance: self.a.p1 = precall (self.pre) (postcall (self.post) (self.a.p1)) –

1

Como han mencionado otros, la solución de envoltura/decorador es probablemente la más fácil. No recomiendo modificar la clase envuelta, por las mismas razones que usted señala.

Si tiene muchas clases externas, puede escribir un generador de código para generar las clases contenedoras por usted.Ya que estás haciendo esto en Python, probablemente puedas incluso implementar el generador como parte del programa, generando los wrappers al inicio, o algo así.

Cuestiones relacionadas