Файл шаблонов. Генераторы
Файл шаблонов
Вы вынесли весь дублирующийся код и добавили достаточно абстракций для возможности хранить правила формирования множественного числа в списке строк. Следующий логический этап — взять эти строки и расположить их в отдельном файле, где они могут поддерживаться отдельно от использующего их кода.
Во-первых, давайте создадим текстовый файл, содержащий нужные нам правила. Никаких сложных структур данных, просто разделенные на три колонки данные. Назовем его plural4-rules. txt
[sxz]$ $ es
[^aeioudgkprt]h$ $ es
[^aeiou]y$ y$ ies
$ $ s
Теперь давайте посмотрим, как вы можете использовать этот файл с правилами.
import re
def build_match_and_apply_functions(pattern, search, replace): ①
def matches_rule(word):
return re. search(pattern, word)
def apply_rule(word):
return re. sub(search, replace, word)
return (matches_rule, apply_rule)
rules = []
with open('plural4-rules. txt', encoding='utf-8') as pattern_file: ②
for line in pattern_file: ③
pattern, search, replace = line. split(None, 3) ④
rules. append(build_match_and_apply_functions( ⑤
pattern, search, replace))
- Функция build_match_and_apply_functions() не изменилась. Вы по-прежнему используете замыкания, чтобы динамически создать две функции, которые будут использовать переменные из внешней функции.
- Глобальная функция open() открывает файл и возвращает файловый объект. В данном случае файл, который мы открываем, содержит строки-шаблоны для правил формирования множественного числа. Утверждение with создает то, что называется контекстом: когда блок with заканчивается, Python автоматически закроет файл, даже если внутри блока with было выброшено исключение. Подробнее о блоках with и файловых объектах вы узнаете из главы Файлы.
- Форма «for line in < fileobject> » читает данные из открытого файла построчно и присваивает текст переменной line. Подробнее про чтение файлов вы узнаете из главы Файлы.
- Каждая строка в файле действительно содержит три значения, но они разделены пустым пространством (табуляцией или пробелами, без разницы). Чтобы разделить их, используйте строковый метод split(). Первый аргумент для split() — None, что означает «разделить любым символом свободного пространства (табуляцией или пробелом, без разницы)». Второй аргумент — 3, что означает «разбить свободным пространством 3 раза, затем оставить остальную часть строки» Строка вида «[sxz]$ $ es» будет разбита и преобразована в список ['[sxz]$', '$', 'es'], что означает что pattern станет '[sxz]$', search — '$', а replace получит значение 'es'. Это довольно мощно для одной маленькой строки кода
- В конце концов, вы передаете pattern, search и replace функции build_match_and_apply_function(), которая возвращает кортеж функций. Вы добавляете этот кортеж в список rules, и в завершении rules хранит список функций поиска совпадений и выполнения замен, который ожидает функция plural().
Сделанное улучшение заключается в том, что вы полностью вынесли правила во внешний файл, так что он может поддерживаться отдельно от использующего его кода. Код — это код, данные — это данные, а жизнь хороша.
Генераторы
Но ведь будет круто если обобщенная функция plural() будет разбирать файл с правилами? Извлеки правила, найди совпадения, примени соответствующие изменения, переходи к следующему правилу. Это все, что функции plural() придется делать, и больше ничего от нее не требуется.
def rules(rules_filename):
with open(rules_filename, encoding='utf-8') as pattern_file:
for line in pattern_file:
pattern, search, replace = line. split(None, 3)
yield build_match_and_apply_functions(pattern, search, replace)
def plural(noun, rules_filename='plural5-rules. txt'):
for matches_rule, apply_rule in rules(rules_filename):
if matches_rule(noun):
return apply_rule(noun)
raise ValueError('no matching rule for {0}'. format(noun))
Как черт возьми это работает? Давайте сначала посмотрим на пример с пояснениями.
> > > def make_counter(x):
... print('entering make_counter')
... while True:
... yield x ①
... print('incrementing x')
... x = x + 1
...
> > > counter = make_counter(2) ②
> > > counter ③
< generator object at 0x001C9C10>
> > > next(counter) ④
entering make_counter
2
> > > next(counter) ⑤
incrementing x
3
> > > next(counter) ⑥
incrementing x
4
- Присутствие ключевого слова yield в make_counter означает, что это не обычная функция. Это особый вид функции, которая генерирует значения по одному. Вы можете думать о ней как о продолжаемой функции. Её вызов вернёт генератор, который может быть использован для генерации последующих значений x.
- Чтобы создать экземпляр генератора make_counter, просто вызовите его как и любую другую функцию. Заметьте, что фактически это не выполняет кода функции. Вы можете так сказать, потому что первая строка функции make_counter() вызывает print(), но ничего до сих пор не напечатано.
- Функция make_counter() возвращает объект-генератор.
- Функция next() принимает генератор и возвращает его следующее значение. Первый раз, когда вы вызываете next() с генератором counter, он исполняет код в make_counter() до первого утверждения yield, затем возвращает значение, которое было возвращено yield. В данном случае, это будет 2, поскольку изначально вы создали генератор вызовом make_counter(2).
- Повторный вызов next() с тем же генератором продолжает вычисления точно там, где они были прерваны, и продолжает до тех пор, пока не встретит следующий yield. Все переменные, локальные состояния и т. д. сохраняются во время yield, и восстанавливаются при вызове next(). Следующая строка кода, ожидающая исполнения, вызывает print(), который печатает incrementing x. После этого следует утверждение x = x + 1. Затем снова исполняется цикл while, и первое, что в нём встречается — утверждение yield x, которое сохраняет все состояние и возвращает текущее значение x (сейчас это 3).
- После второго вызова next(counter) происходит всё то же самое, только теперь x становится равным 4.
Поскольку make_counter входит в бесконечный цикл, вы бы теоретически могли заниматься этим бесконечно, а он продолжал бы увеличивать x и возвращать его значения. Но вместо этого давайте посмотрим на более продуктивное использование генераторов.
Воспользуйтесь поиском по сайту: