Search⌘ K
AI Features

A Plural Rule Iterator

Explore how to implement a plural rules iterator in Python 3 by defining a class with __iter__ and __next__ methods. Understand how to efficiently cache match and apply functions from a pattern file to enhance performance while iterating. This lesson guides you through managing file I/O within iterators and handling class versus instance variables for robust design.

We'll cover the following...

Now it’s time for the finale. Let’s rewrite the plural rules generator as an iterator.

Python 3.5
class LazyRules:
rules_filename = 'plural6-rules.txt'
def __init__(self):
self.pattern_file = open(self.rules_filename, encoding='utf-8')
self.cache = []
def __iter__(self):
self.cache_index = 0
return self
def __next__(self):
self.cache_index += 1
if len(self.cache) >= self.cache_index:
return self.cache[self.cache_index - 1]
if self.pattern_file.closed:
raise StopIteration
line = self.pattern_file.readline()
if not line:
self.pattern_file.close()
raise StopIteration
pattern, search, replace = line.split(None, 3)
funcs = build_match_and_apply_functions(
pattern, search, replace)
self.cache.append(funcs)
return funcs
rules = LazyRules()

So this is a class that implements __iter__() and __next__(), so it can be used as an iterator. Then, you instantiate the class and assign it to rules. This happens just once, on import.

iter(f) calls f.__iter__. next(f) calls f.__next__

Let’s take the class one bite at a time.

Python 3.5
class LazyRules:
rules_filename = 'plural6-rules.txt'
def __init__(self):
self.pattern_file = open(self.rules_filename, encoding='utf-8') #①
self.cache = [] #②

① When we instantiate the LazyRules class, open the pattern file but don’t read anything from it. (That comes later.)

② After opening the patterns file, initialize the cache. You’ll use this cache later (in the __next__() method) as you read lines from the pattern file.

Before we continue, let’s take a closer look at rules_filename. It’s not defined within the __iter__() method. In fact, it’s not defined within any method. It’s defined at the class level. It’s a class variable, and although you can access it just like an instance variable (self.rules_filename), it is shared across all instances of the LazyRules class.

Python 3.5
import plural6
r1 = plural6.LazyRules()
r2 = plural6.LazyRules()
print (r1.rules_filename) #①
#plural6-rules.txt
print (r2.rules_filename)
#plural6-rules.txt
r2.rules_filename = 'r2-override.txt' #②
print (r2.rules_filename)
#r2-override.txt
print (r1.rules_filename)
#plural6-rules.txt
print (r2.__class__.rules_filename ) #③
#plural6-rules.txt
r2.__class__.rules_filename = 'papayawhip.txt' #④
print (r1.rules_filename)
#papayawhip.txt
print (r2.rules_filename) #⑤
#r2-overridetxt

① Each instance of the class inherits the rules_filename attribute with the value defined by the class.

② Changing the attribute’s value in one instance does not affect other instances…

③ …nor does it change the class attribute. You can access the class attribute (as opposed to an individual instance’s attribute) by using the special __class__ attribute to access the class itself.

④ If you change the class attribute, all instances that are still inheriting that value (like r1 here) will be affected.

⑤ Instances that have overridden that attribute (like r2 here) will not be affected.

And now ...