This article is participating in Python Theme Month. See the link to the event for more details

Defer function

We know that there is a keyword in GolangdeferTo declare that before a function is called, it will delay execution of the function until the external function exits

The characteristics of the defer

  1. deferThe function is executed after the outer function has finished executing
package main

import "fmt"

func main(a) {
	defer fmt.Println(2)
	fmt.Println(1)}/* output:
1
2
*/
Copy the code
  1. deferThe function is executed when the outer function exits unexpectedly
package main

import "fmt"

func main(a) {
	defer fmt.Println(2)
	panic(1)}/* output: 2 panic: 1 goroutine 1 [running]: main.main() /tmp/sandbox740231192/prog.go:7 +0x95 */
Copy the code

3. If there are multiple defer functions in the function, they will be executed in LIFO order:

package main

import "fmt"

func main(a) {
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println(1)}/*output:
1
3
2
*/
Copy the code

The purpose of the defer

Release resources

For example, open the file and close the file:

package main

import "os"

func main(a) {
	file, err := os.Open("1.txt")
	iferr ! =nil {
		return
	}
        // Close the file
	defer file.Close()

	// Do something...

}
Copy the code

For example, cancel the connection after the database operation

func createPost(db *gorm.DB) error {
    conn := db.Conn()
    defer db.Close()
    
    err := conn.Create(&Post{Author: "Draveness"}).Error

    return err
}
Copy the code

Recover back

package main

import "fmt"

func main(a) {
    defer func(a) {
        if ok := recover(a); ok ! =nil {
            fmt.Println("recover")}} ()panic("error")}/*output:
recover
*/
Copy the code

conclusion

  1. deferThe function is always executed, regardless of whether the outer function exits normally or if it is panicked
  2. If you have more than onedeferFunctions, which are executed in LIFO order

Write a defer in Python

Since Defer works so well, can Pythoneer own it as well? Of course,

Home (Python) conditions

Python has a library called Contextlib, which has a class called ExitStack.

A context manager that is designed to make it easy to programmatically combine other context managers and cleanup functions, especially those that are optional or otherwise driven by input data. Since registered callbacks are invoked in the reverse order of registration, this ends up behaving as if multiple nested with statements had been used with the registered set of callbacks. This even extends to exception handling – if an inner callback suppresses or replaces an exception, then outer callbacks will be passed arguments based on that updated state.

Since registered callbacks are invoked in the reverse order of registration – the key sentence is that the registered callbacks were invoked in the reverse order of registration, Isn’t this the second LIFO feature of the defer function?

Look again at the ExitStack class:

class ExitStack(ContextManager[ExitStack]) :
    def __init__(self) - >None:.def enter_context(self, cm: ContextManager[_T]) -> _T:.def push(self, exit: _CM_EF) -> _CM_EF:.def callback(self, callback: Callable[...Any], *args: Any, **kwds: Any) - >Callable[...Any] :.def pop_all(self: _U) -> _U:.def close(self) - >None:.def __enter__(self: _U) -> _U:.def __exit__(
        self,
        __exc_type: Optional[Type[BaseException]],
        __exc_value: Optional[BaseException],
        __traceback: Optional[TracebackType],
    ) - >bool:.Copy the code

As you can see, it implements the protocols __enter__ and __exit__ that are context managers, so the first feature of defer is guaranteed: the defer function will always be executed. Let’s test it out

import contextlib

with contextlib.ExitStack() as stack:
   stack.callback(lambda: print(1))
   stack.callback(lambda: print(2))
   
   print("hello world")
   raise Exception()
Copy the code

Output:

hello world
2
1
---------------------------------------------------------------------------
Exception  Traceback (most recent call last)
/defer_test.py in <module>
      6 
      7     print("hello world")
---->8 raise Exception()

Exception: 
Copy the code

nice! This works

action

Let’s encapsulate it and make it more general

Class version, likedeferas

import contextlib


class Defer:
    def __init__(self, *callback) :
        """callback is lambda function """
        self.stack = contextlib.ExitStack()
        for c in callback:
            self.stack.callback(c)

    def __enter__(self) :
        pass

    def __exit__(self, exc_type, exc_val, exc_tb) :
        self.stack.__exit__(exc_type, exc_val, exc_tb)


if __name__ == "__main__":
    with Defer(lambda: print("close file"), lambda: print("close conn")) as d:
        print("hello world")
        raise Exception()
Copy the code

Output:

hello world
close conn
close file
Traceback (most recent call last):
  File "defer.py", line 38, in <module>
    raise Exception()
Exception
Copy the code

By working with lambda expressions, we can be more flexible

Decorator version that does not intrude on function selection

import contextlib def defer(*callbacks): def decorator(func): def wrapper(*args, **kwargs): with contextlib.ExitStack() as stack: for callback in callbacks: stack.callback(callback) return func(*args, **kwargs) return wrapper return decorator @defer(lambda: print("logging"), lambda: print("close conn..." )) def query_exception(db): print("query..." ) raise Exception() if __name__ == "__main__": db = None query_exception(db)Copy the code

Output:

query...
close conn...
logging
Traceback (most recent call last):
  File "defer.py", line 43, in <module>
    query_exception(db)
  File "defer.py", line 25, in wrapper
    return func(*args, **kwargs)
  File "defer.py", line 38, in query_exception
    raise Exception()
Exception
Copy the code

The Get! Learn quickly ~