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

huangapple go评论97阅读模式
英文:

Python equivalent of golang's defer statement

问题

如何在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

Go语言中的用例示例:

var m sync.Mutex
func someFunction() {
    m.Lock()
    defer m.Unlock()
    // 无论你想做什么,可以有多个返回语句,随意放置。
    // 只需忘记你曾经锁定了互斥锁,或者记得要释放它。
}

希望对你有帮助!

英文:

How would one implement something that works like the defer statement from go in python?

Defer pushes a function call to a stack. When the function containing the defer statement returns, the defered function calls are popped and executed one by one, in the scope that the defer statement was inside in the first place. Defer statements look like function calls, but are not executed until they are popped.

Go example of how it works:

func main() {
    fmt.Println(&quot;counting&quot;)

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

	fmt.Println(&quot;done&quot;)
}

Outputs:

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

Go example of a usecase:

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

答案1

得分: 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

希望对你有帮助!

英文:

To emulate defer fmt.Println(*a, i) example, you could use contextlib.ExitStack:

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

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

    x = 42
    a = x
    print(&quot;done&quot;)

Output

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

It is easy to emulate the mutex case:

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

答案2

得分: 18

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

在Python中,相似的代码如下:

mutex = Lock()

def someFunction():
    with mutex:
        # 无论你想做什么,有多少个return语句,随时随地都可以。
        # 只需忘记你曾经锁定了互斥锁,或者需要记住再次释放它。
英文:

Python's with statement serves a similar purpose to Go's defer.

The similar code in Python is:

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.

答案3

得分: 12

我已经制作了一个兼容2.x版本的代码(在此处1):

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

英文:

I've made one there (compatible with 2.x):

@defers_collector
def func():
    f = open(&#39;file.txt&#39;, &#39;w&#39;)
    defer(lambda: f.close())

    defer(lambda : print(&quot;Defer called!&quot;))

    def my_defer():
	recover()

    defer(lambda: my_defer())

    print(&quot;Ok )&quot;)
    panic(&quot;WTF?&quot;)

    print(&quot;Never printed (((&quot;)


func()
print(&quot;Recovered!&quot;)

Source of defers_collector is:

# Go-style error handling

import inspect
import sys

def panic(x):
    raise Exception(x)

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

def recover():
    val = None
    for f in inspect.stack():
	loc = f[0].f_locals
	if f[3] == &#39;__exit__&#39; and &#39;__suppress__&#39; in loc:
	    val = loc[&#39;exc_value&#39;]
	    loc[&#39;__suppress__&#39;].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__

答案4

得分: 9

一个部分受到@DenisKolodin回答启发的defer实现已作为pygolang的一部分可用,2

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

A defer implementation partly inspired by @DenisKolodin answer is available as part of pygolang, 2:

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

答案5

得分: 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)
    # 其他操作

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
英文:

This complement to jfs' answer pushes the ExitStack idea a bit further with the help of decorators:

@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 is defined as follows:

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

答案6

得分: 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)


Usage example:

def main():
    first()
    second()

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

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

if __name__ == '__main__':
    main()

希望对你有帮助!

英文:

I've tried to make an equivalent for fun (only tested as a proof of concept)

Here is is:

import os
import inspect


class defer:
    &quot;&quot;&quot;
    Proof of concept for a python equivalent of golang&#39;s defer statement

    Note that the callback order is probably not guaranteed

    &quot;&quot;&quot;
    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&#39;_&#39; + os.urandom(48)] = self

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

Usage example:

def main():
    first()
    second()

def first():
    print(&#39;- first&#39;)
    defer(lambda: print(&#39;   - deferred&#39;))
    print(&#39;- first exit&#39;)

def second():
    print(&#39;- second&#39;)      

if __name__ == &#39;__main__&#39;:
    main()

答案7

得分: 0

我晚了一点,但我建立了一个可以通过pip安装的库来实现这个!请查看python-defer

from defer import defer

def foo():
  print("Hello, world!") in defer
  print("Hello", end="")
  # 做一些可能失败的事情...
  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
英文:

Little late to the party but I built a pip-installable library that does just this! Check out python-defer.

from defer import defer

def foo():
  print(&quot;, world!&quot;) in defer
  print(&quot;Hello&quot;, end=&quot;&quot;)
  # do something that might fail...
  assert 1 + 1 == 3
$ python foo.py
Hello, World!
Traceback (most recent call last):
  File &quot;foo.py&quot;, line 7, in &lt;module&gt;
    assert 1 + 1 == 3
AssertionError

huangapple
  • 本文由 发表于 2016年1月6日 11:21:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/34625089.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定