Python panic / defer error handling

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


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

    funcCopyFile(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 . In this case, in the reverse order, 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 run 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 that use special variables on the stack to hang handlers on the function and set a special status in case of recovery. To specify the support function of this mechanism, a decorator is used, which creates a list for storing pending functions and catches an exception for calling them. Code:

    # Go-style error handlingimport inspect
    import sys
    defpanic(x):raise Exception(x)
    defdefer(x):for f in inspect.stack():
            if'__defers__'in f[0].f_locals:
                f[0].f_locals['__defers__'].append(x)
                breakdefrecover():
        val = Nonefor 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)
                breakreturn val
    classDefersContainer(object):def__init__(self):# List for sustain refer in shallow clone
            self.defers = []
        defappend(self, defer):
            self.defers.append(defer)
        def__enter__(self):passdef__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__
    defdefers_collector(func):def__wrap__(*args, **kwargs):
            __defers__ = DefersContainer()
            with __defers__:
                func(*args, **kwargs)
        return __wrap__
    @defers_collectordeffunc():
        f = open('file.txt', 'w')
        defer(lambda: f.close())
        defer(lambda : print("Defer called!"))
        defmy_defer():
            recover()
        defer(lambda: my_defer())
        print("Ok )")
        panic("WTF?")
        print("Never printed (((")
    func()
    print("Recovered!")
    


    I use lambda to hold arguments on a deferred call to repeat the behavior of the defer statement .

    I did not test functional identity in the nuances. But if you know what you need to refine, write.

    Also popular now: