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 Golangdefer
To declare that before a function is called, it will delay execution of the function until the external function exits
The characteristics of the defer
defer
The 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
defer
The 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
defer
The function is always executed, regardless of whether the outer function exits normally or if it is panicked- If you have more than one
defer
Functions, 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, likedefer
as
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 ~