Skip to content

Python: генераторы последний рубеж Часть 2 А теперь что то совсем другое

Dmitry Ponyatov edited this page Oct 5, 2019 · 14 revisions

Python: генераторы последний рубеж

Часть 2 А теперь что-то совсем другое

Общий мотив

  • Рассмотрим следующее
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
  • Используется для переформулировки потока управления
  • Это упрощает программирование для других (простое определение контекстных менеджеров)
  • Может быть, есть еще что-то ... (конечно, есть)

Python: генераторы последний рубеж Часть 3 Вызови наверно

Clone this wiki locally