Предлагает ли Python немного синтаксического сахара, чтобы подсластить конструкцию генератора test
, как показано ниже?
def acquire():
print('Acquiring resource')
yield 'A'
def do_stuff():
print('Doing stuff')
yield 'B'
def release():
print('Releasing resource')
yield 'C'
def test():
yield from acquire()
yield from do_stuff()
yield from release()
[u for u in test()] # has value ['A', 'B', 'C']
По сути, мне нужен синтаксис, который позволяет использовать acuire и release в одном выражении. Сначала я подумал, что контекстный менеджер будет уместен, например:
class conman:
def __init__(self, acq, rel):
self.acq = acq
self.rel = rel
def __enter__(self):
try:
while True:
next(self.acq)
except StopIteration:
return
def __exit__(self, _, __, ___):
try:
while True:
next(self.rel)
except StopIteration:
return
def conmantest():
with conman(acquire(), release()):
yield from do_stuff()
[u for u in conmantest()]
Этот подход будет правильно выполнять итерацию через получение и освобождение генераторов, но он не передает результат в контекст. В результате список будет иметь значение ['B']
, хотя он по-прежнему печатает все сообщения в правильном порядке.
Другой подход заключается в использовании декоратора
def manager(acq, rel):
def decorator(func):
def wrapper(*args, **kwargs):
yield from acq
yield from func(*args, **kwargs)
yield from rel
return
return wrapper
return decorator
@manager(acquire(), release())
def do_stuff_decorated():
print('Doing stuff')
yield 'B'
[u for u in do_stuff_decorated()]
Это правильно, но на практике do_stuff — это список операторов, и не всегда желательно писать вокруг них генератор.
Если бы релиз был обычной функцией python, мы могли бы попробовать этот обходной путь:
class conman2:
def __init__(self, acq, rel):
self.acq = acq
self.rel = rel
def __enter__(self):
return self.acq
def __exit__(self, _, __, ___):
self.rel()
def release_func():
print('Releasing stuff')
def conman2test():
with conman2(acquire(), release_func) as r:
yield from r
yield from do_stuff()
[u for u in conmantest()]
Это делает все правильно, так как release_func — это произвольная функция, а не генератор, но нам пришлось передать дополнительный оператор `yield from r'. Нечто подобное используется в библиотеке SimPy для программирования дискретных событий для реализовать контекст для ресурсов, где они автоматически освобождаются, когда контекст заканчивается.
Однако я надеялся, что может быть какой-то синтаксис, например
class yielded_conman:
def __init__(self, acq, rel):
self.acq = acq
self.rel = rel
def __yielded_enter__(self):
yield from self.acq()
def __yielded_exit__(self, _, __, ___):
yield from self.rel()
def yieldconmantest():
with yielded_conman(acquire(), release()):
yield from do_stuff()
[u for u in conmantest()] # has value ['A', 'B', 'C']
который делает все правильно.
do_stuff
на самом деле является одним генератором. (Это имеет место только здесь, потому что это простой пример.) - person Lambda Mu   schedule 25.07.2019