The aim of this problem is the same of the one seen in the previous post, I am going to implement a similar plural() Python function that would get in input a word, as 'boy', and would return its plural form, like 'boys'.
The test case is just the same, plural() would be almost the same, the match_apply() closure I used to return two functions, one for checking the word against the current pattern, the other to apply the relative change to the word, is the same.
The big change is that I won't use a generator but a iterator under the bonnet, with the idea of reading the patterns from file just once, and not any time the user asks for a name pluralization.
So I create a class Rules, implementing the special methods __iter__() and __next__(), that make it both an iterable (for the first) and an iterator (for the latter), and then I would instantiate it in an object that is going to be used by plural(), instead of the generator.
class Rules: filename = 'plural_names_rules.txt' # 1 def __init__(self, filename=None): # 2 if filename: self.filename = filename self.rules =  with open(self.filename) as patterns: # 3 for line in patterns: pattern, search, replace = line.split() self.rules.append(match_apply(pattern, search, replace)) # 4 def __iter__(self): # 5 self.index = -1 return self def __next__(self): # 6 self.index += 1 if len(self.rules) > self.index: return self.rules[self.index] raise StopIteration1. By default, the rules are read from this file.
2. When initializing a Rules object, the user could change the rules file.
3. I have moved here the reading from file, so that it is done only once for each Rules object. Notice the with-as statement that delegates to Python the resource cleanup when exiting the block.
4. For each line in the pattern file, I extract the patter, search, and replace token, I pass them to the match_apply() closure, and I get back the relative match() apply() functions, that I push to the rules list.
5. I am stating that Rules is a Python iterable, providing a way to extract from it an iterator, that is the Rules object itself. Here I initialize the index to the internal list (namely rules) to the first item before the actual beginning (that means, minus one).
6. Rules is also a Python3 iterator. Each time it is required a new value from it, this method is called. If the iterator index is still in the range, the relative item is returned, otherwise, as required by the python iterator desing.
The change in the plural() function is minimal, but dense of meaning.
rules = Rules() # 1 def plural(noun): for match, apply in rules: # 2 if match(noun): return apply(noun) return '???'1. I need a Rules() object to work with. Here I instantiate it.
2. The for-each statement requires an iterable. The object rules has a __iter__() method, so it works fine in this role. Before executing a loop, for-each asks to the iterator provided by rules (that incidentally is rules itself) its next element. Since this is a Python3 script, the __next__() method of the iterator is called, getting the next element in the list of rules, a couple of functions retrieved from the match_apply() closure.
Reference: Dive into Python 3 Chapter 7 Classes & Iterators, sections 6.
Test case and python script on GitHub.