-
Notifications
You must be signed in to change notification settings - Fork 5
Python: генераторы последний рубеж Часть 2 А теперь что то совсем другое
Dmitry Ponyatov edited this page Oct 5, 2019
·
14 revisions
- Рассмотрим следующее
f = open()
...
f.close()
lock.acquire()
...
lock.release()
db.start_transaction()
...
db.commit()
start = time.time()
...
end = time.time()
- Это используется так часто, что вы увидите это везде!
- Выражение
with
with open(filename) as f:
код
код
...
with lock:
код
код
...
- Позволяет контролировать вход / выход блока кода
- Типичное использование: все что указано на предыдущем слайде
- Легко сделать собственный @contextmanager
import time
from contextlib import contextmanager
@contextmanager
def timethis(label):
start = time.time()
try:
yield
finally:
end = time.time()
print('%s: %0.3f' % (label, end-start)
- Этот менеджер контекста профилирует время выполнения блока выражений
- Использование
with timethis('counting'):
n = 1000000
while n > 0:
n -= 1
-
Вывод
counting: 0.023
-
Другой пример: временные каталоги
import tempfile, shutil
from contextlib import contextmanager
@contextmanager
def tempdir():
outdir = tempfile.mkdtemp()
try:
yield outdir
finally:
shutil.rmtree(outdir)
- Пример
with tempdir() as dirname:
...
-
yield outdir
Что это? не итерация, не поток данных, не конкурентность - под капотом
with obj: # --> obj.__enter__()
statements
statements
statements
...
statements
# --> obj.__exit__()
-
Если объект реализует эти методы, он может контролировать вход / выход в блок кода
-
Шаблон реализации
class Manager(object):
def __enter__(self):
return value
def __exit__(self, exc_type, val, tb):
if exc_type is None:
return
else:
# обработайте исключение если хотите
return True if handled else False
- Использование:
with Manager() as value:
statements
statements
- Автоматически удаляемые временные каталоги
import tempfile
import shutil
class tempdir(object):
def __enter__(self):
self.dirname = tempfile.mkdtemp()
return self.dirname
def __exit__(self, exc, val, tb):
shutil.rmtree(self.dirname)
- Использование
with tempdir() as dirname:
...
-
@contextmanager
это просто переформулировка - Это один и тот же код, склеенный по-разному
- Думайте о
yield
как о ножницах - которые режут функцию пополам
- Каждая половина отображается в методы менеджера контекста
__enter__
и__exit__
-
yield
- это магия, которая делает это возможным- класс-обёртка
class GeneratorCM(object):
def __init__(self, gen):
self.gen = gen
def __enter__(self):
...
def __exit__(self, exc, val, tb):
...
- и декоратор
def contextmanager(func):
def run(*args, **kwargs):
return GeneratorCM(func(*args, **kwargs))
return run
-
run
выполняет генератор доyield
- он выполняет один шаг итерации
- контекст-переменная возвращается как параметр
yield
-
exit
возобновляет генератор
class GeneratorCM(object):
...
def __exit__(self, etype, val, tb):
try:
if etype is None:
next(self.gen)
else:
self.gen.throw(etype, val, tb)
raise RuntimeError("Generator didn't stop")
except StopIteration:
return True
except:
if sys.exc_info()[1] is not val: raise
- возобновляет нормально, либо вызывает исключение
- Реальная реализация сложнее
- Есть несколько неприятных частных случаев
- Исключения без ассоциированного значения
-
StopIteration
поднятое внутри блока - Исключения, поднятые в менеджере контекста
- Читайте исходный код и посмотрите PEP-343
- Зачем начинать с этого примера?
- Совершенно другое использование
yield
- Используется для переформулировки потока управления
- Это упрощает программирование для других (простое определение контекстных менеджеров)
- Может быть, есть еще что-то ... (конечно, есть)