Вы поднимаете критику, высказанную до 1. Очистка в этом случае не является детерминированной, но она будет выполняться с CPython, когда генератор собирает мусор. Ваш опыт может отличаться для других реализаций Python ...
Вот небольшой пример:
from __future__ import print_function
import contextlib
@contextlib.contextmanager
def manager():
"""Easiest way to get a custom context manager..."""
try:
print('Entered')
yield
finally:
print('Closed')
def gen():
"""Just a generator with a context manager inside.
When the context is entered, we'll see "Entered" on the console
and when exited, we'll see "Closed" on the console.
"""
man = manager()
with man:
for i in range(10):
yield i
# Test what happens when we consume a generator.
list(gen())
def fn():
g = gen()
next(g)
# g.close()
# Test what happens when the generator gets garbage collected inside
# a function
print('Start of Function')
fn()
print('End of Function')
# Test what happens when a generator gets garbage collected outside
# a function. IIRC, this isn't _guaranteed_ to happen in all cases.
g = gen()
next(g)
# g.close()
print('EOF')
Запустив этот скрипт в CPython, я получаю:
$ python ~/sandbox/cm.py
Entered
Closed
Start of Function
Entered
Closed
End of Function
Entered
EOF
Closed
По сути, мы видим, что для исчерпанных генераторов диспетчер контекста очищает, когда вы ожидаете. Для генераторов, которые не исчерпаны, функция очистки запускается, когда генератор собирается сборщиком мусора. Это происходит, когда генератор выходит за рамки (или, самое позднее, IIRC в следующем gc.collect
цикле).
Однако, проведя несколько быстрых экспериментов (например, запустив приведенный выше код в pypy
), я не очистил все мои контекстные менеджеры:
$ pypy --version
Python 2.7.10 (f3ad1e1e1d62, Aug 28 2015, 09:36:42)
[PyPy 2.6.1 with GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)]
$ pypy ~/sandbox/cm.py
Entered
Closed
Start of Function
Entered
End of Function
Entered
EOF
Таким образом, утверждение, что диспетчер контекста __exit__
будет вызываться для всех реализаций Python, неверно. Вероятно, промахи здесь связаны с мусором pypy стратегия сбора данных (которая не подсчета ссылок), и к тому времени, когда pypy
решит использовать генераторы, процесс уже завершается, и, следовательно, он не беспокоится о нем .. . В большинстве реальных приложений генераторы вероятно будут собраны и завершены достаточно быстро, так что на самом деле это не имеет значения ...
Предоставление строгих гарантий
Если вы хотите гарантировать, что ваш диспетчер контекста завершен правильно, вам следует позаботиться о закройте генератор, когда закончите с ним 2. Раскомментирование строк g.close()
выше дает мне детерминированную очистку, потому что GeneratorExit
поднимается в операторе yield
(который находится внутри диспетчера контекста), а затем он перехватывается / подавляется генератором ...
$ pypy ~/sandbox/cm.py
Entered
Closed
Start of Function
Entered
Closed
End of Function
Entered
Closed
EOF
$ python3 ~/sandbox/cm.py
Entered
Closed
Start of Function
Entered
Closed
End of Function
Entered
Closed
EOF
$ python ~/sandbox/cm.py
Entered
Closed
Start of Function
Entered
Closed
End of Function
Entered
Closed
EOF
FWIW, это означает, что вы можете очистить свои генераторы с помощью contextlib.closing
:
from contextlib import closing
with closing(gen_function()) as items:
for item in items:
pass # Do something useful!
1 В последнее время некоторая дискуссия вращалась вокруг PEP 533, цель которого - сделать очистку итератора более детерминированной.
2 Совершенно нормально закрыть уже закрытый и / или использованный генератор, чтобы вы могли вызвать не беспокоясь о состоянии генератора.
person
mgilson
schedule
26.01.2017
producer=select_files()
), вы можете использовать его метод.throw
, чтобы сообщить ему о выключении. docs.python.org/3/reference/expressions.html#generator. выбросить. - person Terry Jan Reedy   schedule 26.01.2017close
, который лучше служит цели остановки генератора, а не генерирует там случайное исключение ... - person Bakuriu   schedule 27.01.2017with open(...) as f: for line in f: yield line
. Потребитель может не исчерпать генератор и, следовательно, файл не может быть закрыт. Это проблема ленивого ввода-вывода в целом. Лучше открывать файлы внутри нетерпеливого кода и передавать их ленивым функциям. - person Bakuriu   schedule 27.01.2017fileinput
. См. Также stackoverflow.com/questions/16095855/ - person mgilson   schedule 27.01.2017