Python中与golang的defer语句相对应的是什么?

44
如何在Python中实现类似Go语言中的defer语句的功能?
defer语句将一个函数调用推入一个栈中。当包含defer语句的函数返回时,推迟的函数调用会逐个弹出并执行,执行的作用域与defer语句所在的作用域相同。defer语句看起来像函数调用,但只有在弹出时才会执行。
以下是Go语言中的示例:
func main() {
    fmt.Println("counting")

    var a *int
    for i := 0; i < 10; i++ {
        a = &i
        defer fmt.Println(*a, i)
    }

    x := 42
    a = &x

    fmt.Println("done")
}

输出:

counting
done
9 9
8 8
7 7
6 6
5 5
4 4
3 3
2 2
1 1
0 0

一个使用案例的示例:
var m sync.Mutex
func someFunction() {
    m.Lock()
    defer m.Unlock()
    // Whatever you want, with as many return statements as you want, wherever.
    // Simply forget that you ever locked a mutex, or that you have to remember to release it again.
}
7个回答

33

为了模拟defer fmt.Println(*a, i)的示例,您可以使用contextlib.ExitStack

#!/usr/bin/env python3
from contextlib import ExitStack
from functools import partial

print("counting")
with ExitStack() as stack:
    for i in range(10):
        a = i
        stack.callback(partial(print, a, i))

    x = 42
    a = x
    print("done")

输出

counting
done
9 9
8 8
7 7
6 6
5 5
4 4
3 3
2 2
1 1
0 0

模拟互斥锁情况很容易:

def some_function(lock=Lock()):
    with lock:
        # whatever

18

Python的with语句与Go的defer语句有相似的作用。

在Python中,类似的代码是:

mutex = Lock()

def someFunction():
    with mutex:
        # Whatever you want, with as many return statements
        # as you want, wherever. Simply forget that you ever
        # locked a mutex, or that you have to remember to 
        # release it again.

1
这里需要记住的一件事是,你需要定义一个上下文管理器才能使用 with。很多东西(例如 filethreading.Lock)都内置了上下文管理器,但如果你想像第一个例子中那样做一些特定的事情,就必须自定义构建它。 - a p
我创建了这个自定义工具 https://github.com/tintin10q/python_json_database_manager - Quinten C

12

我已经制作了一个这里(与2.x兼容):

@defers_collector
def func():
    f = open('file.txt', 'w')
    defer(lambda: f.close())

    defer(lambda : print("Defer called!"))

    def my_defer():
    recover()

    defer(lambda: my_defer())

    print("Ok )")
    panic("WTF?")

    print("Never printed (((")


func()
print("Recovered!")

defers_collector的来源是:

# Go-style error handling

import inspect
import sys

def panic(x):
    raise Exception(x)

def defer(x):
    for f in inspect.stack():
    if '__defers__' in f[0].f_locals:
        f[0].f_locals['__defers__'].append(x)
        break

def recover():
    val = None
    for 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)
        break
    return val

class DefersContainer(object):
    def __init__(self):
    # List for sustain refer in shallow clone
    self.defers = []

    def append(self, defer):
    self.defers.append(defer)

    def __enter__(self):
    pass

    def __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__


def defers_collector(func):
    def __wrap__(*args, **kwargs):
    __defers__ = DefersContainer()
    with __defers__:
        func(*args, **kwargs)
    return __wrap__

一种受此答案启发的 defer 实现已作为 pygolang 的一部分提供:https://dev59.com/kVsW5IYBdhLWcg3wwZYP#53069630 - kirr

9

一个延迟执行(defer)实现部分灵感来自于@DenisKolodin答案,它是pygolang的一部分,2

   wc = wcfs.join(zurl)    │     wc = wcfs.join(zurl)
   defer(wc.close)         │     try:
                           │        ...
   ...                     │        ...
   ...                     │        ...
   ...                     │     finally:
                           │        wc.close()

5
这篇文章是jfs的回答的补充,借助装饰器将ExitStack的概念进一步推广:
@with_exit_stack
def counting(n, stack):
    for i in range(n):
        stack.callback(print, i)


@with_exit_stack
def locking(lock, stack):
    stack.enter_context(lock)
    # whatever

with_exit_stack的定义如下:

import functools
import contextlib

def with_exit_stack(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        with contextlib.ExitStack() as stack:
            return func(*args, **kwargs, stack=stack)

    return wrapper

3

我尝试制作了一个有趣的等效物(仅作为概念验证测试)

这里是:

import os
import inspect


class defer:
    """
    Proof of concept for a python equivalent of golang's defer statement

    Note that the callback order is probably not guaranteed

    """
    def __init__(self, callback, *args, **kwargs):
        self.callback = callback
        self.args = args
        self.kwargs = kwargs

        # Add a reference to self in the caller variables so our __del__
        # method will be called when the function goes out of scope
        caller = inspect.currentframe().f_back
        caller.f_locals[b'_' + os.urandom(48)] = self

    def __del__(self):
        self.callback(*self.args, **self.kwargs)

使用示例:

def main():
    first()
    second()

def first():
    print('- first')
    defer(lambda: print('   - deferred'))
    print('- first exit')

def second():
    print('- second')      

if __name__ == '__main__':
    main()

0
有点晚来参加派对,但我建立了一个可以通过pip安装的库,可以做到这一点!请查看python-defer
from defer import defer

def foo():
  print(", world!") in defer
  print("Hello", end="")
  # do something that might fail...
  assert 1 + 1 == 3

$ python foo.py
Hello, World!
Traceback (most recent call last):
  File "foo.py", line 7, in <module>
    assert 1 + 1 == 3
AssertionError

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接