📜 ⬆️ ⬇️

Error handling panic / defer style in Python

Error handling in Go is not built on a numbed exception mechanism, but on a new interesting mechanism of deferred handlers. As an interesting study, I implemented this error handling in Python. Who cares, come in.


The principle of error handling in Go is the following, you specify the defer keyword, after which you put a function call that will be executed when the method ends: normal or panic (if an error occurs). Example:

func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst, src) } 

')
You can read more here . When specifying a deferred function, the arguments are fixed, and the call occurs at the end of the function containing them. If you want to interrupt the execution of a function with an error state, you must call the panic () function. Thus, in the reverse order of installation, the deferred functions are called. If the recover () function is called in one of them, then the erroneous state is removed, and after returning from the method, the program will proceed in the usual order.

This behavior can be implemented in Python, due to the flexibility of the language. To do this, the corresponding functions are declared, which use special variables in the stack to hang handlers on the function, and set a special status in case of recovery. To indicate the support function of this mechanism, a decorator is used, which creates a list for storing deferred functions, and intercepts an exception to call them. Code:

 # Go-style error handling import inspect import sys def panic(x): raise Exception(x) def defer(x): for f in inspect.stack(): if '__defers__' in f[0].f_locals: f[0].f_locals['__defers__'].append(x) break def recover(): val = None for f in inspect.stack(): loc = f[0].f_locals if f[3] == '__exit__' and '__suppress__' in loc: val = loc['exc_value'] loc['__suppress__'].append(True) break return val class DefersContainer(object): def __init__(self): # List for sustain refer in shallow clone self.defers = [] def append(self, defer): self.defers.append(defer) def __enter__(self): pass def __exit__(self, exc_type, exc_value, traceback): __suppress__ = [] for d in reversed(self.defers): try: d() except: __suppress__ = [] exc_type, exc_value, traceback = sys.exc_info() return __suppress__ def defers_collector(func): def __wrap__(*args, **kwargs): __defers__ = DefersContainer() with __defers__: func(*args, **kwargs) return __wrap__ @defers_collector def func(): f = open('file.txt', 'w') defer(lambda: f.close()) defer(lambda : print("Defer called!")) def my_defer(): recover() defer(lambda: my_defer()) print("Ok )") panic("WTF?") print("Never printed (((") func() print("Recovered!") 


I use lambda to fix the arguments in a deferred call to repeat the behavior of the defer operator.

Functional identity in the nuances not tested. But if you know what needs to be finalized, write.

Source: https://habr.com/ru/post/191786/


All Articles