Generators

Wouldn’t it be grand to have a generic plural() function that parses the rules file? Get rules, check for a match, apply appropriate transformation, go to next rule. That’s all the plural() function has to do, and that’s all the plural() function should do.

Press + to interact
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))

How the heck does that work? Let’s look at an interactive example first.

Press + to interact
def make_counter(x):
print('entering make_counter')
while True:
yield x #①
print('incrementing x')
x = x + 1
counter = make_counter(2) #②
print (counter) #③
#<generator object at 0x001C9C10>
print (next(counter)) #④
#entering make_counter
#2
print (next(counter)) #⑤
#incrementing x
#3
print (next(counter)) #⑥
#incrementing x
#4

① The presence of the yield keyword in make_counter means that this is not a normal function. It is a special kind of function which generates values one at a time. You can think of it as a resumable function. Calling it will return a generator that can be used to generate successive values of x.

② To create an instance of the make_counter generator, just call it like any other function. Note that this does not actually execute the function code. You can tell this because the first line of the make_counter() function calls print(), but nothing has been printed yet.

③ The make_counter() function returns a generator object.

④ The next() function takes a generator object and returns its next value. The first time you call ...