2010-05-07 4 views
15

come aggiungere codice a una funzione esistente, prima o dopo?Python aggiunge dinamicamente a una funzione

per esempio, ho una classe:

class A(object): 
    def test(self): 
     print "here" 

Come si modifica la metaprogrammazione classe di ingegno in modo che faccio questo

class A(object): 
    def test(self): 
     print "here" 

     print "and here" 

forse un modo di aggiungendo un'altra funzione per testare?

aggiungere un'altra funzione come

def test2(self): 
     print "and here" 

e modificare l'originale per

class A(object): 
    def test(self): 
     print "here" 
     self.test2() 

c'è un modo per fare questo?

risposta

18

È possibile utilizzare un decoratore per modificare la funzione, se si desidera. Tuttavia, poiché non è un decoratore applicato al momento della definizione iniziale della funzione, non sarà possibile utilizzare lo zucchero sintattico @ per applicarlo.

>>> class A(object): 
...  def test(self): 
...   print "orig" 
... 
>>> first_a = A() 
>>> first_a.test() 
orig 
>>> def decorated_test(fn): 
...  def new_test(*args, **kwargs): 
...   fn(*args, **kwargs) 
...   print "new" 
...  return new_test 
... 
>>> A.test = decorated_test(A.test) 
>>> new_a = A() 
>>> new_a.test() 
orig 
new 
>>> first_a.test() 
orig 
new 

Si noti che modificherà anche il metodo per le istanze esistenti.

EDIT: modificata la lista args per il decoratore al versione migliore utilizzando args e kwargs

+0

grazie, questo è quello che mi serviva – Timmy

2

Perché non utilizzare l'ereditarietà?

class B(A): 
    def test(self): 
     super(B, self).test() 
     print "and here" 
+0

il corto è Ho dovuto aggiungere dinamicamente i genitori a una classe, quindi non posso chiamare il super facilmente, senza fare quello che la domanda sta chiedendo in primo luogo – Timmy

+0

abbastanza giusto. I decoratori sembrano decisamente il modo di andare in quel momento. –

10

Il modo tipico per aggiungere funzionalità a una funzione è quello di utilizzare un decorator (utilizzando the wraps function):

from functools import wraps 

def add_message(func): 
    @wraps 
    def with_additional_message(*args, **kwargs) 
     try: 
      return func(*args, **kwargs) 
     finally: 
      print "and here" 
    return with_additional_message 

class A: 
    @add_message 
    def test(self): 
     print "here" 

Naturalmente, dipende da cosa si sta cercando di realizzare. Io uso decoratori molto, ma se tutto quello che volevo fare era stampare i messaggi in più, probabilmente farei qualcosa come

class A: 
    def __init__(self): 
     self.messages = ["here"] 

    def test(self): 
     for message in self.messages: 
      print message 

a = A() 
a.test() # prints "here" 

a.messages.append("and here") 
a.test() # prints "here" then "and here" 

Ciò richiede alcuna meta-programmazione, ma poi di nuovo il tuo esempio era probabilmente molto semplificata da cosa devi veramente fare. Forse se pubblichi più dettagli sulle tue esigenze specifiche, possiamo consigliarti meglio quale sarebbe l'approccio Pythonic.

MODIFICA: poiché sembra che si desideri chiamare le funzioni, è possibile avere un elenco di funzioni anziché un elenco di messaggi. Per esempio:

class A: 
    def __init__(self): 
     self.funcs = [] 

    def test(self): 
     print "here" 
     for func in self.funcs: 
      func() 

def test2(): 
    print "and here" 

a = A() 
a.funcs.append(test2) 
a.test() # prints "here" then "and here" 

Si noti che se si desidera aggiungere funzioni che saranno chiamati da tutte le istanze di A, allora si dovrebbe fare funcs un campo di classe piuttosto che un campo di istanza, ad esempio,

class A: 
    funcs = [] 
    def test(self): 
     print "here" 
     for func in self.funcs: 
      func() 

def test2(): 
    print "and here" 

A.funcs.append(test2) 

a = A() 
a.test() # print "here" then "and here" 
0

Se la classe A eredita da oggetto, si potrebbe fare qualcosa di simile:

def test2(): 
    print "test" 

class A(object): 
    def test(self): 
     setattr(self, "test2", test2) 
     print self.test2 
     self.test2() 

def main(): 
    a = A() 
    a.test() 

if __name__ == '__main__': 
    main() 

Questo codice non ha alcun interesse e non puoi usare te stesso nel tuo nuovo metodo che hai aggiunto. Ho appena trovato divertente questo codice, ma non lo userò mai. Non mi piace cambiare dinamicamente l'oggetto stesso.

È il modo più veloce e più comprensibile.

4

Ci sono molti suggerimenti davvero buoni sopra, ma uno che non ho visto stava passando una funzione con la chiamata. Potrebbe essere simile a questa:

class A(object): 
    def test(self, deep=lambda self: self): 
     print "here" 
     deep(self) 
def test2(self): 
    print "and here" 

Usando questo:

>>> a = A() 
>>> a.test() 
here 
>>> a.test(test2) 
here 
and here 
1

Copia e incolla, godere !!!!!

#!/usr/bin/env python 

def say(host, msg): 
    print '%s says %s' % (host.name, msg) 

def funcToMethod(func, clas, method_name=None): 
    setattr(clas, method_name or func.__name__, func) 

class transplant: 
    def __init__(self, method, host, method_name=None): 
     self.host = host 
     self.method = method 
     setattr(host, method_name or method.__name__, self) 

    def __call__(self, *args, **kwargs): 
     nargs = [self.host] 
     nargs.extend(args) 
     return apply(self.method, nargs, kwargs) 

class Patient: 
    def __init__(self, name): 
     self.name = name 

if __name__ == '__main__': 
    jimmy = Patient('Jimmy') 
    transplant(say, jimmy, 'say1') 
    funcToMethod(say, jimmy, 'say2') 

    jimmy.say1('Hello') 
    jimmy.say2(jimmy, 'Good Bye!') 
1

Questa risposta consente di modificare la funzione originale senza creare un wrapper. Ho trovato i seguenti due tipi pitone nativi, che sono utili per rispondere alla tua domanda:

types.FunctionType 

E

types.CodeType 

Questo codice sembra fare il lavoro:

import inspect 
import copy 
import types 
import dill 
import dill.source 


#Define a function we want to modify: 
def test(): 
    print "Here" 

#Run the function to check output 
print '\n\nRunning Function...' 
test() 
#>>> Here 

#Get the source code for the test function: 
testSource = dill.source.getsource(test) 
print '\n\ntestSource:' 
print testSource 


#Take the inner part of the source code and modify it how we want: 
newtestinnersource = '' 
testSourceLines = testSource.split('\n') 
linenumber = 0 
for line in testSourceLines: 
    if (linenumber > 0): 
     if (len(line[4:]) > 0): 
      newtestinnersource += line[4:] + '\n' 
    linenumber += 1 
newtestinnersource += 'print "Here2"' 
print '\n\nnewtestinnersource' 
print newtestinnersource 


#Re-assign the function's code to be a compiled version of the `innersource` 
code_obj = compile(newtestinnersource, '<string>', 'exec') 
test.__code__ = copy.deepcopy(code_obj) 
print '\n\nRunning Modified Function...' 
test() #<- NOW HAS MODIFIED SOURCE CODE, AND PERFORMS NEW TASK 
#>>>Here 
#>>>Here2 
+0

Mi trovo tuttavia aggriviato dal fatto che la mia soluzione non funzioni per più di un iterazione di un ciclo di modifica delle funzioni. dill.source.getsource funziona solo sulla versione originale della funzione e, dopo aver modificato la funzione, non può essere utilizzata nuovamente per ottenere il codice sorgente. Sembrerebbe che Python tenga traccia solo del codice sorgente originariamente compilato e NON tenga traccia delle modifiche del codice sorgente a una funzione. In linea di principio, potresti prendere la versione compilata della funzione e quindi ricostruire il codice Python ... ma sembra sciocco. –