Comment pourrait-on implémenter quelque chose qui fonctionne comme l'instruction defer
de go en python?
Defer pousse un appel de fonction vers une pile. Lorsque la fonction contenant l'instruction defer revient, les appels de fonction différée sont extraits et exécutés un par un, dans la portée dans laquelle se trouvait l'instruction defer en premier lieu. Les instructions de report ressemblent à des appels de fonction, mais ne sont exécutées que lorsqu'elles sont sautées.
Exemple de fonctionnement:
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")
}
Les sorties:
counting
done
9 9
8 8
7 7
6 6
5 5
4 4
3 3
2 2
1 1
0 0
Exemple de cas d'utilisation:
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.
}
Pour émuler l'exemple defer fmt.Println(*a, i)
, vous pouvez tilisez 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
Il est facile d'émuler le cas mutex:
def some_function(lock=Lock()):
with lock:
# whatever
Python avec instruction sert un objectif similaire à Go de reporter.
Le code similaire dans Python est:
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.
J'en ai fait un là (compatible avec 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!")
Source de defers_collector
est:
# 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__
Une implémentation defer partiellement inspirée de @ DenisKolodinanswer est disponible dans le cadre de pygolang , 2 :
wc = wcfs.join(zurl) │ wc = wcfs.join(zurl)
defer(wc.close) │ try:
│ ...
... │ ...
... │ ...
... │ finally:
│ wc.close()
Ce complément à réponse de jfs pousse l'idée ExitStack
un peu plus loin avec l'aide de décorateurs:
@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
est défini comme suit:
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