2012-02-02 15 views
10

Ho due classi (chiamiamole Working and ReturnStatement) che non posso modificare, ma voglio estenderle entrambe con il logging. Il trucco è che il metodo Working restituisce un oggetto ReturnStatement, quindi il nuovo oggetto MutantWorking restituisce anche ReturnStatement a meno che non possa lanciarlo su MutantReturnStatement. Dire con il codice:Come eseguire il cast dell'oggetto in 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 

Il risultato atteso:
sto avvolgendo di lavoro.
Sto lavorando.
-
Sto avvolgendo ReturnStatement.
Sono un ReturnStatement.

È possibile risolvere il problema? Sono anche curioso di sapere se il problema può essere risolto anche in PHP. A meno che non ottenga una soluzione funzionante, non posso accettare la risposta, quindi per favore scrivi un codice funzionante per essere accettato.

+6

"Casting" non esiste in Python. –

+0

Inoltre, non dovresti eseguire "' ReturnStatement(). Act() '" - se vuoi che il metodo act attivi sull'altra classe per lavorare sull'oggetto corrente, esegui 'returnStatement.act (self)' - o semplicemente contrassegnalo come classmethod o staticmethod - se non ha bisogno di un'istanza dell'oggetto corrente. – jsbueno

+0

Working.do() restituisce con ReturnStatement. Voglio MutantWorking.do() per tornare con un MutantReturnStatement. So che il casting non esiste in Python, ma il problema esiste. C'è una soluzione? – Visko

risposta

5

Non c'è il casting come le altre risposte già spiegate. Puoi creare sottoclassi o creare nuovi tipi modificati con la funzionalità extra usando i decoratori .

Ecco un esempio completo (credito a How to make a chain of function decorators?). Non è necessario modificare le classi originali. Nel mio esempio la classe originale si chiama 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() 

questo stampa

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 

Inoltre, vorrei capire perché non è possibile modificare una classe. Hai provato? Perché, come alternativa a fare una sottoclasse, se si sente dinamico si possibile quasi sempre modificare una vecchia classe sul posto:

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

Aggiornamento: Applicare la registrazione a tutti i metodi di una classe

Come te lo hai chiesto espressamente. È possibile possibile eseguire il ciclo su tutti i membri e applicare la registrazione a tutti loro. Ma è necessario definire una regola per quale tipo di membri modificare. L'esempio seguente esclude qualsiasi metodo con __ nel suo nome.

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 questo tutto ciò che dovete fare per rendere una nuova versione archiviati di una classe è:

@loggify 
class loggedWorker(Working): pass 

o modificare una classe esistente in atto:

loggify(Working) 
+1

Penso che tu abbia capito il mio problema e finora questa sembra essere la risposta. Grazie! – Visko

+0

Dopo il mio iniziale entusiasmo, ho realizzato tristemente che questa non è una soluzione per il mio problema. Non posso modificare la Working class usando decoratori per restituire MutantReturnStatement invece di ReturnStatement, perché quella era la condizione iniziale (Working non può essere modificata). – Visko

+0

@Visko, penso che tu abbia letto male la mia risposta allora. Il mio "loghello" è esattamente il "MutantWorking" che hai richiesto. Non è necessario modificare 'Working' come non ho modificato 'ciao'. Se pensi che questo non sia ancora chiaro o se non sei d'accordo, posso provare ad aggiornare la mia risposta. –

2

Non c'è "casting" in Python. Qualsiasi sottoclasse di una classe è considerata un'istanza dei suoi genitori. Il comportamento desiderato può essere ottenuto chiamando correttamente i metodi della superclasse e ignorando gli attributi della classe.

Cosa si può fare sul tuo esempio, è quello di avere un inizializzatore sottoclasse che riceve la superclasse e copia i suoi attributi importanti - così, la vostra MutantReturnstatement potrebbe essere scritto così:

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

e quindi modificare la tua classe MutantWorking a:

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

ci sono modi divinatorio per non avere un sacco di self.attr = other.attr linee sul metodo __init__ se ci sono un sacco (come, più di 3 :-)) attribuisce da copiare - 0.123.il più pigro di cui basta semplicemente copiare l'attributo __dict__ di un'altra istanza.

In alternativa, se si sa cosa si sta facendo, si potrebbe anche semplicemente cambiare l'attributo __class__ del vostro oggetto di destinazione per la classe desiderata - ma che può essere fuorviante e si portano a errori sottili (il metodo della __init__ la sottoclasse non verrebbe chiamata, non funzionerebbe su classi non definite da python, e altri possibili problemi), non consiglio questo approccio - questo non è "casting", è l'uso dell'introspezione per bruteforce una modifica dell'oggetto ed è solo incluso per tenere la risposta completa:

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

Ancora una volta - questo dovrebbe funzionare, ma non farlo - utilizzare il metodo rmer.

A proposito, non ho molta esperienza con altri linguaggi OO, che consentono il casting, ma il casting in una sottoclasse è consentito anche in qualsiasi lingua? Ha senso? Penso che il casting sia permesso solo ai corsi per i genitori.

+0

Potresti inserire la risposta nel codice? Altrimenti non penso che tu abbia risposto alla mia domanda. – Visko

+0

@jsbueno, sì * puoi * definire un cast da qualsiasi oggetto a qualsiasi oggetto, ad esempio C++. –

+0

Non conosco necessariamente gli attributi delle classi non mutanti. Diciamo che sono librerie C++ importate in Python. – Visko

1

Nessun modo diretto.

È possibile definire init di MutantReturnStatement come questo:

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

e quindi utilizzarlo in questo modo:

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

E si dovrebbe sbarazzarsi di ereditare ReturnStatement nel vostro involucro, in questo modo

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

Sono nuovo in Python, ci possono essere alcuni errori nel codice, ma mostra l'idea – Jurlie

+0

Mi piace la tua risposta! Se nessuno dice un'altra (migliore) tecnica, accetterò questo :). – Visko

+0

+1. @Visko, +1 se ti piace qualcosa. (puoi farlo più di una volta.) Inoltre, con i decoratori puoi farlo senza la necessità di creare un tale blocco di codice per ogni metodo. –

0

È don' Ho bisogno di casting qui. Hai solo bisogno di

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

Questo ovviamente darà il corretto ritorno e la stampa desiderata.

+0

Non è una buona soluzione perché devo tornare con uno stesso oggetto di stato (le proprietà non sono incluse nel mio esempio per preservare la semplicità). Questo restituisce una nuova istanza, che non è ciò che volevo. – Visko

Problemi correlati