天天看點

Python也可以擁有延遲函數

延遲函數defer

我們知道在Golang中有一個關鍵字

defer

,用它來聲明在函數調用前,會讓函數*延遲**到外部函數退出時再執行,注意,這裡的退出含義:函數return傳回或者函數panic退出

Python也可以擁有延遲函數

defer的特性

  1. defer

    函數會在外層函數執行結束後執行
package main

import "fmt"

func main() {
	defer fmt.Println(2)
	fmt.Println(1)

}
/* output:
1
2
*/
           
  1. defer

    函數會在外層函數異常退出時執行
package main

import "fmt"

func main() {
	defer fmt.Println(2)
	panic(1)

}
/* output:
2
panic: 1

goroutine 1 [running]:
main.main()
	/tmp/sandbox740231192/prog.go:7 +0x95
*/
           

3.如果函數中有多個

defer

函數,它們的執行順序是LIFO:

package main

import "fmt"

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

defer的用途

釋放資源

比如打開檔案後關閉檔案:

package main

import "os"

func main() {
	file, err := os.Open("1.txt")
	if err != nil {
		return
	}
        // 關閉檔案
	defer file.Close()

	// Do something...

}
           

又比如資料庫操作操作後取消連接配接

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

    return err
}
           

recover恢複

package main

import "fmt"

func main() {
    defer func() {
        if ok := recover(); ok != nil {
            fmt.Println("recover")
        }
    }()

    panic("error")
}
/*output:
recover
*/
           

總結

  1. defer

    函數總會被執行,無論外層函數是正常退出還是異常panic
  2. 如果函數中有多個

    defer

    函數,它們的執行順序是LIFO

在Python中的寫一個defer

看到

defer

這麼好用,Pythoneer也可以擁有嗎?當然

家裡(Python)的條件

Python中有一個庫叫做

contextlib

,它有一個類叫

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 這句是關鍵,他說注冊的回調函數是以注冊順序相反的順序被調用,這不就是

defer

函數的第二個特性LIFO嗎?

再看下

ExitStack

類:

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: ...
           

可以看到它實作了是上下文管理器的協定

__enter__

__exit__

,是以是可以保證

defer

的第一個特性:

defer

函數總會被執行

讓我們來測試一下

import contextlib

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

輸出:

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

Exception: 
           

nice! 這行的通

行動

讓我們做一下封裝,讓它更通用一些吧

類版本,像

defer

那樣

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()
           
hello world
close conn
close file
Traceback (most recent call last):
  File "defer.py", line 38, in <module>
    raise Exception()
Exception
           

通過配合

lambda

表達式,我們可以更加靈活

裝飾器版本,不侵入函數的選擇

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)
           
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
           

Get!快學起來吧~