延遲函數defer
我們知道在Golang中有一個關鍵字
defer
,用它來聲明在函數調用前,會讓函數*延遲**到外部函數退出時再執行,注意,這裡的退出含義:函數return傳回或者函數panic退出

defer的特性
-
函數會在外層函數執行結束後執行defer
package main
import "fmt"
func main() {
defer fmt.Println(2)
fmt.Println(1)
}
/* output:
1
2
*/
-
函數會在外層函數異常退出時執行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
*/
總結
-
函數總會被執行,無論外層函數是正常退出還是異常panicdefer
- 如果函數中有多個
函數,它們的執行順序是LIFOdefer
在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
那樣
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!快學起來吧~