2012-02-02 18 views
10

Tengo dos clases (llamémoslas Working y ReturnStatement) que no puedo modificar, pero quiero extenderlas con el registro. El truco es que el método Working devuelve un objeto ReturnStatement, por lo que el nuevo objeto MutantWorking también devuelve ReturnStatement a menos que yo pueda convertirlo a MutantReturnStatement. Diciendo con código:Cómo lanzar objetos en Python

# these classes can't be changed 
class ReturnStatement(object): 
    def act(self): 
     print "I'm a ReturnStatement." 

class Working(object): 
    def do(self): 
     print "I am Working." 
     return ReturnStatement() 

# these classes should wrap the original ones 
class MutantReturnStatement(ReturnStatement): 
    def act(self): 
     print "I'm wrapping ReturnStatement." 
     return ReturnStatement().act() 

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     # !!! this is not working, I'd need that casting working !!! 
     return (MutantReturnStatement) Working().do() 

rs = MutantWorking().do() #I can use MutantWorking just like Working 
print "--" # just to separate output 
rs.act() #this must be MutantReturnState.act(), I need the overloaded method 

El resultado esperado:
estoy envolviendo trabajo.
Estoy trabajando.
-
Estoy modificando ReturnStatement.
Soy un declaración de devolución.

¿Es posible resolver el problema? También tengo curiosidad si el problema también se puede resolver en PHP. A menos que obtenga una solución que funcione, no puedo aceptar la respuesta, por lo tanto, escriba el código de trabajo para que me acepten.

+6

"Casting" no existe en Python. –

+0

Además, no debe hacer "' ReturnStatement(). Act() '" - si desea que el método act en la otra clase funcione en el objeto actual, haga 'returnStatement.act (self)' - o simplemente márquelo como método de clase o estático: si no necesita una instancia del objeto actual. – jsbueno

+0

Working.do() devuelve con un ReturnStatement. Quiero que MutantWorking.do() vuelva con un MutantReturnStatement. Sé que el casting no existe en Python, pero el problema sí. ¿Hay una solución? – Visko

Respuesta

5

No hay fundición como las otras respuestas ya explicadas. Puede crear subclases o hacer nuevos tipos modificados con la funcionalidad adicional usando decoradores .

Aquí hay un ejemplo completo (crédito a How to make a chain of function decorators?). No necesita modificar sus clases originales. En mi ejemplo, la clase original se llama Working.

# decorator for logging 
def logging(func): 
    def wrapper(*args, **kwargs): 
     print func.__name__, args, kwargs 
     res = func(*args, **kwargs) 
     return res 
    return wrapper 

# this is some example class you do not want to/can not modify 
class Working: 
    def Do(c): 
     print("I am working") 
    def pr(c,printit): # other example method 
     print(printit) 
    def bla(c):   # other example method 
     c.pr("saybla") 

# this is how to make a new class with some methods logged: 
class MutantWorking(Working): 
    pr=logging(Working.pr) 
    bla=logging(Working.bla) 
    Do=logging(Working.Do) 

h=MutantWorking() 
h.bla() 
h.pr("Working")             
h.Do() 

esto imprimirá

h.bla() 
bla (<__main__.MutantWorking instance at 0xb776b78c>,) {} 
pr (<__main__.MutantWorking instance at 0xb776b78c>, 'saybla') {} 
saybla 

pr (<__main__.MutantWorking instance at 0xb776b78c>, 'Working') {} 
Working 

Do (<__main__.MutantWorking instance at 0xb776b78c>,) {} 
I am working 

Además, me gustaría entender por qué no se puede modificar una clase. ¿Has probado? Porque, como alternativa a hacer una subclase, si se siente dinámico que puede casi siempre modificar una clase de edad en su lugar:

Working.Do=logging(Working.Do) 
ReturnStatement.Act=logging(ReturnStatement.Act) 

Actualización: Aplicar el registro de todos los métodos de una clase

Como ahora lo solicitó específicamente. Usted puede recorrer todos los miembros y aplicar el registro a todos ellos. Pero necesita definir una regla para qué tipo de miembros modificar. El siguiente ejemplo excluye cualquier método con __ en su nombre.

import types 
def hasmethod(obj, name): 
    return hasattr(obj, name) and type(getattr(obj, name)) == types.MethodType 

def loggify(theclass): 
    for x in filter(lambda x:"__" not in x, dir(theclass)): 
    if hasmethod(theclass,x): 
     print(x) 
     setattr(theclass,x,logging(getattr(theclass,x))) 
    return theclass 

Con esto todo lo que tiene que hacer para hacer una nueva versión de sesión de una clase es:

@loggify 
class loggedWorker(Working): pass 

o modificar una clase existente en el lugar:

loggify(Working) 
+1

Creo que entendiste mi problema y hasta ahora esta parece ser LA RESPUESTA. ¡Gracias! – Visko

+0

Después de mi entusiasmo inicial, tristemente me di cuenta de que esta no es una solución para mi problema. No puedo modificar la clase trabajadora usando decoradores para devolver MutantReturnStatement en lugar de ReturnStatement, porque esa era la condición inicial (Working no se puede modificar). – Visko

+0

@Visko, creo que leyó mal mi respuesta entonces. Mi 'loghello' es exactamente el 'MutantWorking' que pediste. No tiene que modificar 'Trabajar' del mismo modo que no modifiqué 'Hola'. Si crees que esto aún no está claro o si no estás de acuerdo, puedo intentar actualizar mi respuesta. –

2

No hay "casting" en Python. Cualquier subclase de una clase se considera una instancia de sus padres. El comportamiento deseado se puede lograr llamando adecuadamente a los métodos de superclase y anulando los atributos de clase.

Lo que puede hacer en su ejemplo, es tener que tener un inicializador subclase que recibe la superclase y las copias de sus atributos relevantes - es así, su MutantReturnstatement podría escribirse así:

class MutantReturnStatement(ReturnStatement): 
    def __init__(self, previous_object=None): 
     if previous_object: 
      self.attribute = previous_object.attribute 
      # repeat for relevant attributes 
    def act(self): 
     print "I'm wrapping ReturnStatement." 
     return ReturnStatement().act() 

Y luego cambiar su MutantWorking clase a:

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     return MutantReturnStatement(Working().do()) 

hay maneras Pythonic para no tener una gran cantidad de líneas self.attr = other.attr en el método __init__ si hay un montón (como, más de 3 :-)) atributos que desea copiar - , el más simple de los cuales sería simplemente copiar el atributo __dict__ de la otra instancia.

Alternativamente, si usted sabe lo que está haciendo, que también podría simplemente cambiar el atributo __class__ de su objeto de destino a la clase deseada - pero que puede ser engañoso y que llevan a errores sutiles (el método de la __init__ subclase no se llamaría, no funcionaría en clases definidas que no son python, y otros posibles problemas), no recomiendo este enfoque - esto no es "fundición", es el uso de la introspección para aplicar fuerza bruta a un cambio de objeto y es solo incluido para mantener la completa respuesta:

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     result = Working.do(self) 
     result.__class__ = MutantReturnStatement 
     return result 

Una vez más - esto debería funcionar, pero no hacerlo - utilizar el fo método del autor.

Por cierto, no tengo demasiada experiencia con otros lenguajes de OO, que permiten el moldeado, ¿pero el lanzamiento a una subclase está permitido en cualquier idioma? ¿Tiene sentido? Creo que el casting s solo está permitido para clases de padres.

+0

¿Podría publicar la respuesta en el código? De lo contrario, no creo que hayas respondido a mi pregunta. – Visko

+0

@jsbueno, sí * puede * definir un elenco desde cualquier objeto a cualquier objeto, por ejemplo, C++. –

+0

No necesariamente conozco los atributos de las clases no mutantes. Digamos que son bibliotecas C++ importadas a Python. – Visko

1

Sin camino directo.

Es posible definir init del MutantReturnStatement así:

def __init__(self, retStatement): 
    self.retStatement = retStatement 

y luego usarlo como esto:

class MutantWorking(Working): 
    def do(self): 
     print "I am wrapping Working." 
     # !!! this is not working, I'd need that casting working !!! 
     return MutantReturnStatement(Working().do()) 

Y debe deshacerse de heredar ReturnStatement en su envoltorio, como este

class MutantReturnStatement(object): 
    def act(self): 
     print "I'm wrapping ReturnStatement." 
     return self.retStatement.act() 
+0

Soy nuevo en Python, puede haber algunos errores en el código, pero muestra la idea – Jurlie

+0

¡Me gusta tu respuesta! Si nadie dice otra (mejor) técnica, aceptaré esto :). – Visko

+0

+1. @Visko, +1 si te gusta algo. (Puede hacerlo más de una vez). Además, con los decoradores puede hacer esto sin la necesidad de hacer uno de esos bloques de código para cada método. –

0

Usted don' Necesito casting aquí. Solo necesita

class MutantWorking(Working): 
def do(self): 
    print "I am wrapping Working." 
    Working().do() 
    return MutantReturnStatement() 

Esto obviamente dará el retorno correcto y la impresión deseada.

+0

No es una buena solución porque tengo que volver con un mismo objeto de estado (las propiedades no están incluidas en mi ejemplo para preservar la simplicidad). Esto devuelve una nueva instancia, que no es lo que yo quería. – Visko