2015-09-30 24 views
5

Ho uno scenario in cui eseguo dinamicamente funzioni in fase di esecuzione e devo tenere traccia di un ambito "localizzato". Nell'esempio seguente, "startScope" e "endScope" creerebbero effettivamente livelli di "nidificazione" (in realtà, le cose contenute in questo ambito localizzato non sono dichiarazioni di stampa ... sono le chiamate di funzione che inviano dati altrove e il nidificazione è tracciato lì. startScope/endScope imposta semplicemente i flag di controllo che vengono utilizzati per iniziare/terminare la profondità di annidamento corrente).È possibile creare un ambito dinamico localizzato in Python?

Questo funziona perfettamente per il rilevamento dei dati nidificati, tuttavia le eccezioni sono un'altra cosa. Idealmente, un'eccezione si tradurrebbe in una "caduta" dell'attuale ambito localizzato e non nella fine dell'intera funzione (myFunction nell'esempio seguente).

def startScope(): 
    #Increment our control object's (not included in this example) nesting depth 
    control.incrementNestingDepth() 

def endScope(): 
    #Decrement our control object's (not included in this example) nesting depth 
    control.decrementNestingDepth() 

def myFunction(): 
    print "A" 
    print "B" 

    startScope() 
    print "C" 
    raise Exception 
    print "D" 
    print "This print statement and the previous one won't get printed" 
    endScope() 

    print "E" 

def main(): 
    try: 
     myFunction() 
    except: 
     print "Error!" 

L'esecuzione di questo sarebbe (teoricamente) uscita la seguente:

>>> main() 
A 
B 
C 
Error! 
E 

>>> 

Sono abbastanza certo questo non è possibile, come ho scritto sopra - Volevo solo dipingere un quadro di il tipo di risultato finale che sto cercando di ottenere.

È qualcosa del genere possibile in Python?

Edit: A più rilevante (anche se lunga) esempio di come questo viene effettivamente utilizzato:

class Log(object): 
    """ 
    Log class 
    """ 

    def __init__(self): 
     #DataModel is defined elsewhere and contains a bunch of data structures/handles nested data/etc... 
     self.model = DataModel() 

    def Warning(self, text): 
     self.model.put("warning", text) 

    def ToDo(self, text): 
     self.model.put("todo", text) 

    def Info(self, text): 
     self.model.put("info", text) 

    def StartAdvanced(self): 
     self.model.put("startadvanced") 

    def EndAdvanced(self): 
     self.model.put("endadvanced") 

    def AddDataPoint(self, data): 
     self.model.put("data", data) 

    def StartTest(self): 
     self.model.put("starttest") 

    def EndTest(self): 
     self.model.put("endtest") 

    def Error(self, text): 
     self.model.put("error", text) 


#myScript.py 

from Logger import Log 

def test_alpha(): 
    """ 
    Crazy contrived example 

    In this example, there are 2 levels of nesting...everything up to StartAdvanced(), 
    and after EndAdvanced() is included in the top level...everything between the two is 
    contained in a separate level. 
    """ 

    Log.Warning("Better be careful here!") 
    Log.AddDataPoint(fancyMath()[0]) 

    data = getSerialData() 

    if data: 
     Log.Info("Got data, let's continue with an advanced test...") 

     Log.StartAdvanced() 

     #NOTE: If something breaks in one of the following methods, then GOTO (***) 
     operateOnData(data) 
     doSomethingCrazy(data) 
     Log.ToDo("Fill in some more stuff here later...") 
     Log.AddDataPoint(data) 

     Log.EndAdvanced() 

    #(***) Ideally, we would resume here if an exception is raised in the above localized scope 
    Log.Info("All done! Log some data and wrap everything up!") 
    Log.AddDataPoint({"data": "blah"}) 

    #Done 


#framework.py 

import inspect 
from Logger import Log 

class Framework(object): 

    def __init__(self): 
     print "Framework init!" 
     self.tests = [] 

    def loadTests(self, file): 
     """ 
     Simplifying this for the sake of clarity 
     """ 

     for test in file: 
      self.tests.append(test) 

    def runTests(self): 
     """ 
     Simplifying this for the sake of clarity 
     """ 

     #test_alpha() as well as any other user tests will be run here 
     for test in self.tests: 
      Log.StartTest() 

      try: 
       test() 
      except Exception,e : 
       Log.Error(str(e)) 

      Log.EndTest() 

#End 
+0

Potete chiarire quali aspetti di questo sono must-have e quali sono flessibili? Inoltre, hai bisogno/vuoi che questi "ambiti" abbiano un impatto su qualcosa di diverso dalla gestione delle eccezioni? Se l'eccezione è rilevata in 'main', non è possibile ottenere' print "E" 'per eseguire; una volta propagata l'eccezione, non è possibile "riprendere" l'esecuzione a un livello inferiore. – BrenBarn

+0

Per questo esempio, sono interessato solo al risultato. L'esempio (come scritto sopra) chiaramente non funzionerà per il motivo che hai appena menzionato. Per quanto riguarda la flessibilità, non ho il controllo diretto sul contenuto di "myFunction" ... tutto quello che so è che "startScope" e "endScope" saranno chiamati, e posso gestire quelle chiamate in qualsiasi modo ritenga opportuno. Attualmente, questo significa spingere/scoppiare da una pila in una classe di controllo separata. Funziona per i dati, ma la gestione delle eccezioni è un'altra storia. La mia speranza è che ci sia una sorta di schema di progettazione comune che si occupa di questo genere di cose ... – Novark

risposta

3

È possibile ottenere un effetto simile con un gestore di contesto utilizzando un'istruzione with. Qui uso il contextlib.contextmanager decoratore:

@contextlib.contextmanager 
def swallower(): 
    try: 
     yield 
    except ZeroDivisionError: 
     print("We stopped zero division error") 

def foo(): 
    print("This error will be trapped") 
    with swallower(): 
     print("Here comes error") 
     1/0 
     print("This will never be reached") 
    print("Merrily on our way") 
    with swallower(): 
     print("This error will propagate") 
     nonexistentName 
    print("This won't be reached") 

>>> foo() 
This error will be trapped 
Here comes error 
We stopped zero division error 
Merrily on our way 
This error will propagate 
Traceback (most recent call last): 
    File "<pyshell#4>", line 1, in <module> 
    foo() 
    File "<pyshell#3>", line 10, in foo 
    nonexistentName 
NameError: global name 'nonexistentName' is not defined 

Non può essere fatto con una chiamata di funzione normale come nel tuo esempio. Nell'esempio, la funzione startScope viene restituita prima dell'esecuzione del resto del corpo di myFunction, pertanto startScope non può avere alcun effetto su di esso. Per gestire le eccezioni, è necessario un qualche tipo di struttura esplicita (o una dichiarazione with o una normale try/except) all'interno di myFunction; non c'è modo di fare in modo che una semplice funzione richiami magicamente le eccezioni che vengono sollevate nel suo chiamante.

Si consiglia di leggere su context managers come sembrano adattarsi a ciò che si sta tentando di fare. I metodi __enter__ e __exit__ del gestore di contesto corrispondono al numero startScope e endScope. Se farà esattamente ciò che desideri dipende esattamente da ciò che vuoi fare con quelle funzioni "manager", ma probabilmente avrai più fortuna a farlo con un gestore di contesto piuttosto che provare a farlo con semplici chiamate di funzione.

+0

Questo è probabilmente il più vicino possibile al risultato desiderato. Penso ... Sfortunatamente, non ho il controllo diretto su foo() come nell'esempio sopra riportato. "foo" sarebbe scritto da qualcun altro, e l'obiettivo è di essere in grado di dare loro accesso ad alcuni metodi magici che gestiscono il contesto per loro. Quindi chiamerebbero semplicemente "startMagic()", "errorFunc()" e quindi "endMagic()". Il mio obiettivo è quello di assicurarmi che quando le cose si rompono, cadiamo in "endMagic" e saltiamo qualsiasi altra cosa contenuta in "errorFunc" dopo che è stata sollevata l'eccezione. – Novark

+0

@Novark: non è necessario il controllo diretto su 'pippo'. Se stai scrivendo un'API per gli scrittori di 'foo' da usare, penso che potresti farlo funzionare. Quello che dovresti fare è dare loro l'accesso al gestore di contesto, in modo che invece di 'startMagic() ... endMagic()' lo fanno 'con magicManager: ...'. Ti incoraggio a sperimentare con una soluzione di gestione del contesto. Che tipo di caso pensi che non gestirà? – BrenBarn

+0

Ho incluso un esempio esteso nel mio post originale che è un po 'più rilevante per ciò che sto lavorando ... Questo esempio mostra come funzionano le cose, tuttavia, ha il problema di cadere * all * la via d'uscita la funzione test_alpha() quando si incontra un errore, piuttosto che la fine dell'ambito locale (si spera che abbia senso - fammi sapere se posso chiarire qualsiasi cosa) – Novark

Problemi correlati