<_sre.SRE_Match object at 01071D98>

1. This pattern starts out the same as the previous one, checking for the beginning of the string (^), then the

thousands place (M?M?M?). Then it has the new part, in parentheses, which defines a set of three mutually

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

exclusive patterns, separated by vertical bars: CM, CD, and D?C?C?C? (which is an optional D followed by zero

to three optional C characters). The regular expression parser checks for each of these patterns in order

(from left to right), takes the first one that matches, and ignores the rest.

2. 'MCM' matches because the first M matches, the second and third M characters are ignored, and the CM

matches (so the CD and D?C?C?C? patterns are never even considered). MCM is the Roman numeral

representation of 1900.

3. 'MD' matches because the first M matches, the second and third M characters are ignored, and the D?C?C?C?

pattern matches D (each of the three C characters are optional and are ignored). MD is the Roman numeral

representation of 1500.

4. 'MMMCCC' matches because all three M characters match, and the D?C?C?C? pattern matches CCC (the D is

optional and is ignored). MMMCCC is the Roman numeral representation of 3300.

5. 'MCMC' does not match. The first M matches, the second and third M characters are ignored, and the CM

matches, but then the $ does not match because you’re not at the end of the string yet (you still have an

unmatched C character). The C does not match as part of the D?C?C?C? pattern, because the mutually

exclusive CM pattern has already matched.

6. Interestingly, an empty string still matches this pattern, because all the M characters are optional and ignored,

and the empty string matches the D?C?C?C? pattern where all the characters are optional and ignored.

135

Whew! See how quickly regular expressions can get nasty? And you’ve only covered the thousands and

hundreds places of Roman numerals. But if you followed all that, the tens and ones places are easy, because

they’re exactly the same pattern. But let’s look at another way to express the pattern.

5.4. USING THE {n,m} SYNTAX

In the previous section, you were dealing with a pattern

where the same character could be repeated up to

three times. There is another way to express this in

regular expressions, which some people find more

readable. First look at the method we already used in

{1,4}

the previous example.

>>> import re

matches

>>> pattern = '^M?M?M?$'

between 1

>>> re.search(pattern, 'M')

<_sre.SRE_Match object at 0x008EE090>

and 4

>>> pattern = '^M?M?M?$'

>>> re.search(pattern, 'MM')

occurrences

<_sre.SRE_Match object at 0x008EEB48>

>>> pattern = '^M?M?M?$'

of a pattern.

>>> re.search(pattern, 'MMM')

<_sre.SRE_Match object at 0x008EE090>

>>> re.search(pattern, 'MMMM')

>>>

1. This matches the start of the string, and then the first optional M, but not the second and third M (but that’s

okay because they’re optional), and then the end of the string.

2. This matches the start of the string, and then the first and second optional M, but not the third M (but that’s

okay because it’s optional), and then the end of the string.

136

3. This matches the start of the string, and then all three optional M, and then the end of the string.

4. This matches the start of the string, and then all three optional M, but then does not match the end of the

string (because there is still one unmatched M), so the pattern does not match and returns None.

>>> pattern = '^M{0,3}$'

>>> re.search(pattern, 'M')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MM')

<_sre.SRE_Match object at 0x008EE090>

>>> re.search(pattern, 'MMM')

<_sre.SRE_Match object at 0x008EEDA8>

>>> re.search(pattern, 'MMMM')

>>>

1. This pattern says: “Match the start of the string, then anywhere from zero to three M characters, then the

end of the string.” The 0 and 3 can be any numbers; if you want to match at least one but no more than

three M characters, you could say M{1,3}.

2. This matches the start of the string, then one M out of a possible three, then the end of the string.

3. This matches the start of the string, then two M out of a possible three, then the end of the string.

4. This matches the start of the string, then three M out of a possible three, then the end of the string.

5. This matches the start of the string, then three M out of a possible three, but then does not match the end of

the string. The regular expression allows for up to only three M characters before the end of the string, but

you have four, so the pattern does not match and returns None.

5.4.1. CHECKING FOR TENS AND ONES

Now let’s expand the Roman numeral regular expression to cover the tens and ones place. This example

shows the check for tens.

137

>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$'

>>> re.search(pattern, 'MCMXL')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MCML')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MCMLX')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MCMLXXX')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MCMLXXXX')

>>>

1. This matches the start of the string, then the first optional M, then CM, then XL, then the end of the string.

Remember, the (A|B|C) syntax means “match exactly one of A, B, or C”. You match XL, so you ignore the

XC and L?X?X?X? choices, and then move on to the end of the string. MCMXL is the Roman numeral

representation of 1940.

2. This matches the start of the string, then the first optional M, then CM, then L?X?X?X?. Of the L?X?X?X?, it

matches the L and skips all three optional X characters. Then you move to the end of the string. MCML is the

Roman numeral representation of 1950.

3. This matches the start of the string, then the first optional M, then CM, then the optional L and the first

optional X, skips the second and third optional X, then the end of the string. MCMLX is the Roman numeral

representation of 1960.

4. This matches the start of the string, then the first optional M, then CM, then the optional L and all three

optional X characters, then the end of the string. MCMLXXX is the Roman numeral representation of 1980.

5. This matches the start of the string, then the first optional M, then CM, then the optional L and all three

optional X characters, then fails to match the end of the string because there is still one more X unaccounted

for. So the entire pattern fails to match, and returns None. MCMLXXXX is not a valid Roman numeral.

138

The expression for the ones place follows the same

pattern. I’ll spare you the details and show you the end

result.

(A|B)

matches

either

pattern A or

pattern B,

but not

both.

>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'

So what does that look like using this alternate {n,m} syntax? This example shows the new syntax.

>>> pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$'

>>> re.search(pattern, 'MDLV')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MMDCLXVI')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MMMDCCCLXXXVIII')

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'I')

<_sre.SRE_Match object at 0x008EEB48>

139

1. This matches the start of the string, then one of a possible three M characters, then D?C{0,3}. Of that, it

matches the optional D and zero of three possible C characters. Moving on, it matches L?X{0,3} by matching

the optional L and zero of three possible X characters. Then it matches V?I{0,3} by matching the optional V

and zero of three possible I characters, and finally the end of the string. MDLV is the Roman numeral

representation of 1555.

2. This matches the start of the string, then two of a possible three M characters, then the D?C{0,3} with a D

and one of three possible C characters; then L?X{0,3} with an L and one of three possible X characters;

then V?I{0,3} with a V and one of three possible I characters; then the end of the string. MMDCLXVI is the

Roman numeral representation of 2666.

3. This matches the start of the string, then three out of three M characters, then D?C{0,3} with a D and three

out of three C characters; then L?X{0,3} with an L and three out of three X characters; then V?I{0,3}

with a V and three out of three I characters; then the end of the string. MMMDCCCLXXXVIII is the Roman

numeral representation of 3888, and it’s the longest Roman numeral you can write without extended syntax.

4. Watch closely. (I feel like a magician. “Watch closely, kids, I’m going to pull a rabbit out of my hat.”) This

matches the start of the string, then zero out of three M, then matches D?C{0,3} by skipping the optional D

and matching zero out of three C, then matches L?X{0,3} by skipping the optional L and matching zero out

of three X, then matches V?I{0,3} by skipping the optional V and matching one out of three I. Then the

end of the string. Whoa.

If you followed all that and understood it on the first try, you’re doing better than I did. Now imagine trying

to understand someone else’s regular expressions, in the middle of a critical function of a large program. Or

even imagine coming back to your own regular expressions a few months later. I’ve done it, and it’s not a

pretty sight.

Now let’s explore an alternate syntax that can help keep your expressions maintainable.

140

5.5. VERBOSE REGULAR EXPRESSIONS

So far you’ve just been dealing with what I’ll call “compact” regular expressions. As you’ve seen, they are

difficult to read, and even if you figure out what one does, that’s no guarantee that you’ll be able to

understand it six months later. What you really need is inline documentation.

Python allows you to do this with something called verbose regular expressions. A verbose regular expression

is different from a compact regular expression in two ways:

• Whitespace is ignored. Spaces, tabs, and carriage returns are not matched as spaces, tabs, and carriage

returns. They’re not matched at all. (If you want to match a space in a verbose regular expression, you’ll

need to escape it by putting a backslash in front of it.)

• Comments are ignored. A comment in a verbose regular expression is just like a comment in Python code:

it starts with a # character and goes until the end of the line. In this case it’s a comment within a multi-line

string instead of within your source code, but it works the same way.

This will be more clear with an example. Let’s revisit the compact regular expression you’ve been working

with, and make it a verbose regular expression. This example shows how.

141

>>> pattern = '''

^ # beginning of string

M{0,3} # thousands - 0 to 3 Ms

(CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 Cs),

# or 500-800 (D, followed by 0 to 3 Cs)

(XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 Xs),

# or 50-80 (L, followed by 0 to 3 Xs)

(IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 Is),

# or 5-8 (V, followed by 0 to 3 Is)

$ # end of string

'''

>>> re.search(pattern, 'M', re.VERBOSE)

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MCMLXXXIX', re.VERBOSE)

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'MMMDCCCLXXXVIII', re.VERBOSE)

<_sre.SRE_Match object at 0x008EEB48>

>>> re.search(pattern, 'M')

1. The most important thing to remember when using verbose regular expressions is that you need to pass an

extra argument when working with them: re.VERBOSE is a constant defined in the re module that signals

that the pattern should be treated as a verbose regular expression. As you can see, this pattern has quite a

bit of whitespace (all of which is ignored), and several comments (all of which are ignored). Once you ignore

the whitespace and the comments, this is exactly the same regular expression as you saw in the previous

section, but it’s a lot more readable.

2. This matches the start of the string, then one of a possible three M, then CM, then L and three of a possible

three X, then IX, then the end of the string.

3. This matches the start of the string, then three of a possible three M, then D and three of a possible three C,

then L and three of a possible three X, then V and three of a possible three I, then the end of the string.

4. This does not match. Why? Because it doesn’t have the re.VERBOSE flag, so the re.search function is

treating the pattern as a compact regular expression, with significant whitespace and literal hash marks.

Python can’t auto-detect whether a regular expression is verbose or not. Python assumes every regular

expression is compact unless you explicitly state that it is verbose.

142

5.6. CASE STUDY: PARSING PHONE NUMBERS

So far you’ve concentrated on matching whole patterns.

Either the pattern matches, or it doesn’t. But regular

expressions are much more powerful than that. When a

regular expression does match, you can pick out specific

pieces of it. You can find out what matched where.

\d matches

This example came from another real-world problem I

any numeric

encountered, again from a previous day job. The

problem: parsing an American phone number. The client

digit (0–9).

wanted to be able to enter the number free-form (in a

single field), but then wanted to store the area code,

\D matches

trunk, number, and optionally an extension separately in

the company’s database. I scoured the Web and found

anything

many examples of regular expressions that purported to

do this, but none of them were permissive enough.

but digits.

Here are the phone numbers I needed to be able to

accept:

• 800-555-1212

• 800 555 1212

• 800.555.1212

• (800) 555-1212

• 1-800-555-1212

• 800-555-1212-1234

• 800-555-1212x1234

• 800-555-1212 ext. 1234

• work 1-(800) 555.1212 #1234

143

Quite a variety! In each of these cases, I need to know that the area code was 800, the trunk was 555, and

the rest of the phone number was 1212. For those with an extension, I need to know that the extension

was 1234.

Let’s work through developing a solution for phone number parsing. This example shows the first step.

>>> phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})$')

>>> phonePattern.search('800-555-1212').groups()

('800', '555', '1212')

>>> phonePattern.search('800-555-1212-1234')

>>> phonePattern.search('800-555-1212-1234').groups()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AttributeError: 'NoneType' object has no attribute 'groups'

1. Always read regular expressions from left to right. This one matches the beginning of the string, and then

(\d{3}). What’s \d{3}? Well, \d means “any numeric digit” (0 through 9). The {3} means “match exactly

three numeric digits”; it’s a variation on the {n,m} syntax you saw earlier. Putting it all in parentheses means “match exactly three numeric digits, and then remember them as a group that I can ask for later”. Then

match a literal hyphen. Then match another group of exactly three digits. Then another literal hyphen. Then

another group of exactly four digits. Then match the end of the string.

2. To get access to the groups that the regular expression parser remembered along the way, use the

groups() method on the object that the search() method returns. It will return a tuple of however many

groups were defined in the regular expression. In this case, you defined three groups, one with three digits,

one with three digits, and one with four digits.

3. This regular expression is not the final answer, because it doesn’t handle a phone number with an extension

on the end. For that, you’ll need to expand the regular expression.

4. And this is why you should never “chain” the search() and groups() methods in production code. If the

search() method returns no matches, it returns None, not a regular expression match object. Calling None.groups() raises a perfectly obvious exception: None doesn’t have a groups() method. (Of course, it’s

slightly less obvious when you get this exception from deep within your code. Yes, I speak from experience

here.)

144

>>> phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})-(\d+)$')

>>> phonePattern.search('800-555-1212-1234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('800 555 1212 1234')

>>>

>>> phonePattern.search('800-555-1212')

>>>

1. This regular expression is almost identical to the previous one. Just as before, you match the beginning of

the string, then a remembered group of three digits, then a hyphen, then a remembered group of three

digits, then a hyphen, then a remembered group of four digits. What’s new is that you then match another

hyphen, and a remembered group of one or more digits, then the end of the string.

2. The groups() method now returns a tuple of four elements, since the regular expression now defines four

groups to remember.

3. Unfortunately, this regular expression is not the final answer either, because it assumes that the different

parts of the phone number are separated by hyphens. What if they’re separated by spaces, or commas, or

dots? You need a more general solution to match several different types of separators.

4. Oops! Not only does this regular expression not do everything you want, it’s actually a step backwards,

because now you can’t parse phone numbers without an extension. That’s not what you wanted at all; if the

extension is there, you want to know what it is, but if it’s not there, you still want to know what the

different parts of the main number are.

The next example shows the regular expression to handle separators between the different parts of the

phone number.

>>> phonePattern = re.compile(r'^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$')

>>> phonePattern.search('800 555 1212 1234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('800-555-1212-1234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('80055512121234')

>>>

>>> phonePattern.search('800-555-1212')

>>>

145

1. Hang on to your hat. You’re matching the beginning of the string, then a group of three digits, then \D+.

What the heck is that? Well, \D matches any character except a numeric digit, and + means “1 or more”. So

\D+ matches one or more characters that are not digits. This is what you’re using instead of a literal hyphen,

to try to match different separators.

2. Using \D+ instead of - means you can now match phone numbers where the parts are separated by spaces

instead of hyphens.

3. Of course, phone numbers separated by hyphens still work too.

4. Unfortunately, this is still not the final answer, because it assumes that there is a separator at all. What if the

phone number is entered without any spaces or hyphens at all?

5. Oops! This still hasn’t fixed the problem of requiring extensions. Now you have two problems, but you can

solve both of them with the same technique.

The next example shows the regular expression for handling phone numbers without separators.

>>> phonePattern = re.compile(r'^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')

>>> phonePattern.search('80055512121234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('800.555.1212 x1234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('800-555-1212').groups()

('800', '555', '1212', '')

>>> phonePattern.search('(800)5551212 x1234')

>>>

1. The only change you’ve made since that last step is changing all the + to *. Instead of \D+ between the parts

of the phone number, you now match on \D*. Remember that + means “1 or more”? Well, * means “zero

or more”. So now you should be able to parse phone numbers even when there is no separator character

at all.

2. Lo and behold, it actually works. Why? You matched the beginning of the string, then a remembered group

of three digits (800), then zero non-numeric characters, then a remembered group of three digits (555), then

zero non-numeric characters, then a remembered group of four digits (1212), then zero non-numeric

characters, then a remembered group of an arbitrary number of digits (1234), then the end of the string.

3. Other variations work now too: dots instead of hyphens, and both a space and an x before the extension.

146

4. Finally, you’ve solved the other long-standing problem: extensions are optional again. If no extension is found,

the groups() method still returns a tuple of four elements, but the fourth element is just an empty string.

5. I hate to be the bearer of bad news, but you’re not finished yet. What’s the problem here? There’s an extra

character before the area code, but the regular expression assumes that the area code is the first thing at

the beginning of the string. No problem, you can use the same technique of “zero or more non-numeric

characters” to skip over the leading characters before the area code.

The next example shows how to handle leading characters in phone numbers.

>>> phonePattern = re.compile(r'^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')

>>> phonePattern.search('(800)5551212 ext. 1234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('800-555-1212').groups()

('800', '555', '1212', '')

>>> phonePattern.search('work 1-(800) 555.1212 #1234')

>>>

1. This is the same as in the previous example, except now you’re matching \D*, zero or more non-numeric

characters, before the first remembered group (the area code). Notice that you’re not remembering these

non-numeric characters (they’re not in parentheses). If you find them, you’ll just skip over them and then

start remembering the area code whenever you get to it.

2. You can successfully parse the phone number, even with the leading left parenthesis before the area code.

(The right parenthesis after the area code is already handled; it’s treated as a non-numeric separator and

matched by the \D* after the first remembered group.)

3. Just a sanity check to make sure you haven’t broken anything that used to work. Since the leading characters

are entirely optional, this matches the beginning of the string, then zero non-numeric characters, then a

remembered group of three digits (800), then one non-numeric character (the hyphen), then a remembered

group of three digits (555), then one non-numeric character (the hyphen), then a remembered group of four

digits (1212), then zero non-numeric characters, then a remembered group of zero digits, then the end of

the string.

4. This is where regular expressions make me want to gouge my eyes out with a blunt object. Why doesn’t

this phone number match? Because there’s a 1 before the area code, but you assumed that all the leading

characters before the area code were non-numeric characters (\D*). Aargh.

147

Let’s back up for a second. So far the regular expressions have all matched from the beginning of the string.

But now you see that there may be an indeterminate amount of stuff at the beginning of the string that you

want to ignore. Rather than trying to match it all just so you can skip over it, let’s take a different approach:

don’t explicitly match the beginning of the string at all. This approach is shown in the next example.

>>> phonePattern = re.compile(r'(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')

>>> phonePattern.search('work 1-(800) 555.1212 #1234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('800-555-1212').groups()

('800', '555', '1212', '')

>>> phonePattern.search('80055512121234').groups()

('800', '555', '1212', '1234')

1. Note the lack of ^ in this regular expression. You are not matching the beginning of the string anymore.

There’s nothing that says you need to match the entire input with your regular expression. The regular

expression engine will do the hard work of figuring out where the input string starts to match, and go from

there.

2. Now you can successfully parse a phone number that includes leading characters and a leading digit, plus any

number of any kind of separators around each part of the phone number.

3. Sanity check. This still works.

4. That still works too.

See how quickly a regular expression can get out of control? Take a quick glance at any of the previous

iterations. Can you tell the difference between one and the next?

While you still understand the final answer (and it is the final answer; if you’ve discovered a case it doesn’t

handle, I don’t want to know about it), let’s write it out as a verbose regular expression, before you forget

why you made the choices you made.

148

>>> phonePattern = re.compile(r'''

# don't match beginning of string, number can start anywhere

(\d{3}) # area code is 3 digits (e.g. '800')

\D* # optional separator is any number of non-digits

(\d{3}) # trunk is 3 digits (e.g. '555')

\D* # optional separator

(\d{4}) # rest of number is 4 digits (e.g. '1212')

\D* # optional separator

(\d*) # extension is optional and can be any number of digits

$ # end of string

''', re.VERBOSE)

>>> phonePattern.search('work 1-(800) 555.1212 #1234').groups()

('800', '555', '1212', '1234')

>>> phonePattern.search('800-555-1212')

('800', '555', '1212', '')

1. Other than being spread out over multiple lines, this is exactly the same regular expression as the last step,

so it’s no surprise that it parses the same inputs.

2. Final sanity check. Yes, this still works. You’re done.

5.7. SUMMARY

This is just the tiniest tip of the iceberg of what regular expressions can do. In other words, even though

you’re completely overwhelmed by them now, believe me, you ain’t seen nothing yet.

You should now be familiar with the following techniques:

• ^ matches the beginning of a string.

• $ matches the end of a string.

• \b matches a word boundary.

149

• \d matches any numeric digit.

• \D matches any non-numeric character.

• x? matches an optional x character (in other words, it matches an x zero or one times).

• x* matches x zero or more times.

• x+ matches x one or more times.

• x{n,m} matches an x character at least n times, but not more than m times.

• (a|b|c) matches exactly one of a, b or c.

• (x) in general is a remembered group. You can get the value of what matched by using the groups() method

of the object returned by re.search.

Regular expressions are extremely powerful, but they are not the correct solution for every problem. You

should learn enough about them to know when they are appropriate, when they will solve your problems,

and when they will cause more problems than they solve.

150

CHAPTER 6. CLOSURES & GENERATORS

My spelling is Wobbly. It’s good spelling but it Wobbles, and the letters get in the wrong places.

— Winnie-the-Pooh

6.1. DIVING IN

HavinggrownupthesonofalibrarianandanEnglishmajor,Ihavealwaysbeenfascinatedby

languages. Not programming languages. Well yes, programming languages, but also natural languages. Take

English. English is a schizophrenic language that borrows words from German, French, Spanish, and Latin (to

name a few). Actually, “borrows” is the wrong word; “pillages” is more like it. Or perhaps

“assimilates” — like the Borg. Yes, I like that.

We are the Borg. Your linguistic and etymological distinctiveness will be added to our own.

Resistance is futile.

In this chapter, you’re going to learn about plural nouns. Also, functions that return other functions,

advanced regular expressions, and generators. But first, let’s talk about how to make plural nouns. (If you

haven’t read the chapter on regular expressions, now would be a good time. This chapter assumes you understand the basics of regular expressions, and it quickly descends into more advanced uses.)

If you grew up in an English-speaking country or learned English in a formal school setting, you’re probably

familiar with the basic rules:

• If a word ends in S, X, or Z, add ES. Bass becomes basses, fax becomes faxes, and waltz becomes waltzes.

• If a word ends in a noisy H, add ES; if it ends in a silent H, just add S. What’s a noisy H? One that gets

combined with other letters to make a sound that you can hear. So coach becomes coaches and rash

becomes rashes, because you can hear the CH and SH sounds when you say them. But cheetah becomes

cheetahs, because the H is silent.

151

• If a word ends in Y that sounds like I, change the Y to IES; if the Y is combined with a vowel to sound like

something else, just add S. So vacancy becomes vacancies, but day becomes days.

• If all else fails, just add S and hope for the best.

(I know, there are a lot of exceptions. Man becomes men and woman becomes women, but human becomes humans. Mouse becomes mice and louse becomes lice, but house becomes houses. Knife becomes knives and wife becomes wives, but lowlife becomes lowlifes. And don’t even get me started on words that are their own plural, like sheep, deer, and haiku.)

Other languages, of course, are completely different.

Let’s design a Python library that automatically pluralizes English nouns. We’ll start with just these four rules,

but keep in mind that you’ll inevitably need to add more.

6.2. I KNOW, LET’S USE REGULAR EXPRESSIONS!

So you’re looking at words, which, at least in English, means you’re looking at strings of characters. You

have rules that say you need to find different combinations of characters, then do different things to them.

This sounds like a job for regular expressions!

152

import re

def plural(noun):

if re.search('[sxz]$', noun):

return re.sub('$', 'es', noun)

elif re.search('[^aeioudgkprt]h$', noun):

return re.sub('$', 'es', noun)

elif re.search('[^aeiou]y$', noun):

return re.sub('y$', 'ies', noun)

else:

return noun + 's'

1. This is a regular expression, but it uses a syntax you didn’t see in Regular Expressions. The square brackets mean “match exactly one of these characters.” So [sxz] means “s, or x, or z”, but only one of them. The $

should be familiar; it matches the end of string. Combined, this regular expression tests whether noun ends

with s, x, or z.

2. This re.sub() function performs regular expression-based string substitutions.

Let’s look at regular expression substitutions in more detail.

>>> import re

>>> re.search('[abc]', 'Mark')

<_sre.SRE_Match object at 0x001C1FA8>

>>> re.sub('[abc]', 'o', 'Mark')

'Mork'

>>> re.sub('[abc]', 'o', 'rock')

'rook'

>>> re.sub('[abc]', 'o', 'caps')

'oops'

1. Does the string Mark contain a, b, or c? Yes, it contains a.

2. OK, now find a, b, or c, and replace it with o. Mark becomes Mork.

3. The same function turns rock into rook.

153

4. You might think this would turn caps into oaps, but it doesn’t. re.sub replaces all of the matches, not just

the first one. So this regular expression turns caps into oops, because both the c and the a get turned into

o.

And now, back to the plural() function…

def plural(noun):

if re.search('[sxz]$', noun):

return re.sub('$', 'es', noun)

elif re.search('[^aeioudgkprt]h$', noun):

return re.sub('$', 'es', noun)

elif re.search('[^aeiou]y$', noun):

return re.sub('y$', 'ies', noun)

else:

return noun + 's'

1. Here, you’re replacing the end of the string (matched by $) with the string es. In other words, adding es to

the string. You could accomplish the same thing with string concatenation, for example noun + 'es', but I

chose to use regular expressions for each rule, for reasons that will become clear later in the chapter.

2. Look closely, this is another new variation. The ^ as the first character inside the square brackets means

something special: negation. [^abc] means “any single character except a, b, or c”. So [^aeioudgkprt]

means any character except a, e, i, o, u, d, g, k, p, r, or t. Then that character needs to be followed by h,

followed by end of string. You’re looking for words that end in H where the H can be heard.

3. Same pattern here: match words that end in Y, where the character before the Y is not a, e, i, o, or u.

You’re looking for words that end in Y that sounds like I.

Let’s look at negation regular expressions in more detail.

154

>>> import re

>>> re.search('[^aeiou]y$', 'vacancy')

<_sre.SRE_Match object at 0x001C1FA8>

>>> re.search('[^aeiou]y$', 'boy')

>>>

>>> re.search('[^aeiou]y$', 'day')

>>>

>>> re.search('[^aeiou]y$', 'pita')

>>>

1. vacancy matches this regular expression, because it ends in cy, and c is not a, e, i, o, or u.

2. boy does not match, because it ends in oy, and you specifically said that the character before the y could

not be o. day does not match, because it ends in ay.

3. pita does not match, because it does not end in y.

>>> re.sub('y$', 'ies', 'vacancy')

'vacancies'

>>> re.sub('y$', 'ies', 'agency')

'agencies'

>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy')

'vacancies'

1. This regular expression turns vacancy into vacancies and agency into agencies, which is what you

wanted. Note that it would also turn boy into boies, but that will never happen in the function because you

did that re.search first to find out whether you should do this re.sub.

2. Just in passing, I want to point out that it is possible to combine these two regular expressions (one to find

out if the rule applies, and another to actually apply it) into a single regular expression. Here’s what that

would look like. Most of it should look familiar: you’re using a remembered group, which you learned in

Case study: Parsing Phone Numbers. The group is used to remember the character before the letter y. Then in the substitution string, you use a new syntax, \1, which means “hey, that first group you remembered?

put it right here.” In this case, you remember the c before the y; when you do the substitution, you

substitute c in place of c, and ies in place of y. (If you have more than one remembered group, you can

use \2 and \3 and so on.)

155

Regular expression substitutions are extremely powerful, and the \1 syntax makes them even more powerful.

But combining the entire operation into one regular expression is also much harder to read, and it doesn’t

directly map to the way you first described the pluralizing rules. You originally laid out rules like “if the

word ends in S, X, or Z, then add ES”. If you look at this function, you have two lines of code that say “if

the word ends in S, X, or Z, then add ES”. It doesn’t get much more direct than that.

6.3. A LIST OF FUNCTIONS

Now you’re going to add a level of abstraction. You started by defining a list of rules: if this, do that,

otherwise go to the next rule. Let’s temporarily complicate part of the program so you can simplify another

part.

156

import re

def match_sxz(noun):

return re.search('[sxz]$', noun)

def apply_sxz(noun):

return re.sub('$', 'es', noun)

def match_h(noun):

return re.search('[^aeioudgkprt]h$', noun)

def apply_h(noun):

return re.sub('$', 'es', noun)

def match_y(noun):

return re.search('[^aeiou]y$', noun)

def apply_y(noun):

return re.sub('y$', 'ies', noun)

def match_default(noun):

return True

def apply_default(noun):

return noun + 's'

rules = ((match_sxz, apply_sxz),

(match_h, apply_h),

(match_y, apply_y),

(match_default, apply_default)

)

def plural(noun):

for matches_rule, apply_rule in rules:

157

if matches_rule(noun):

return apply_rule(noun)

1. Now, each match rule is its own function which returns the results of calling the re.search() function.

2. Each apply rule is also its own function which calls the re.sub() function to apply the appropriate

pluralization rule.

3. Instead of having one function (plural()) with multiple rules, you have the rules data structure, which is a

sequence of pairs of functions.

4. Since the rules have been broken out into a separate data structure, the new plural() function can be

reduced to a few lines of code. Using a for loop, you can pull out the match and apply rules two at a time

(one match, one apply) from the rules structure. On the first iteration of the for loop, matches_rule will

get match_sxz, and apply_rule will get apply_sxz. On the second iteration (assuming you get that far),

matches_rule will be assigned match_h, and apply_rule will be assigned apply_h. The function is

guaranteed to return something eventually, because the final match rule (match_default) simply returns

True, meaning the corresponding apply rule (apply_default) will always be applied.

The reason this technique works is that everything in

Python is an object, including functions. The rules data

structure contains functions — not names of functions,

but actual function objects. When they get assigned in

the for loop, then matches_rule and apply_rule are

The “rules”

actual functions that you can call. On the first iteration

of the for loop, this is equivalent to calling

variable is a

matches_sxz(noun), and if it returns a match, calling

apply_sxz(noun).

sequence of

If this additional level of abstraction is confusing, try

pairs of

unrolling the function to see the equivalence. The entire

for loop is equivalent to the following:

functions.

158

def plural(noun):

if match_sxz(noun):

return apply_sxz(noun)

if match_h(noun):

return apply_h(noun)

if match_y(noun):

return apply_y(noun)

if match_default(noun):

return apply_default(noun)

The benefit here is that the plural() function is now simplified. It takes a sequence of rules, defined

elsewhere, and iterates through them in a generic fashion.

1. Get a match rule

2. Does it match? Then call the apply rule and return the result.

3. No match? Go to step 1.

The rules could be defined anywhere, in any way. The plural() function doesn’t care.

Now, was adding this level of abstraction worth it? Well, not yet. Let’s consider what it would take to add a

new rule to the function. In the first example, it would require adding an if statement to the plural()

function. In this second example, it would require adding two functions, match_foo() and apply_foo(), and

then updating the rules sequence to specify where in the order the new match and apply functions should

be called relative to the other rules.

But this is really just a stepping stone to the next section. Let’s move on…

159

6.4. A LIST OF PATTERNS

Defining separate named functions for each match and apply rule isn’t really necessary. You never call them

directly; you add them to the rules sequence and call them through there. Furthermore, each function

follows one of two patterns. All the match functions call re.search(), and all the apply functions call

re.sub(). Let’s factor out the patterns so that defining new rules can be easier.

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)

1. build_match_and_apply_functions() is a function that builds other functions dynamically. It takes

pattern, search and replace, then defines a matches_rule() function which calls re.search() with the

pattern that was passed to the build_match_and_apply_functions() function, and the word that was

passed to the matches_rule() function you’re building. Whoa.

2. Building the apply function works the same way. The apply function is a function that takes one parameter,

and calls re.sub() with the search and replace parameters that were passed to the

build_match_and_apply_functions() function, and the word that was passed to the apply_rule()

function you’re building. This technique of using the values of outside parameters within a dynamic function is

called closures. You’re essentially defining constants within the apply function you’re building: it takes one

parameter (word), but it then acts on that plus two other values (search and replace) which were set

when you defined the apply function.

3. Finally, the build_match_and_apply_functions() function returns a tuple of two values: the two functions

you just created. The constants you defined within those functions (pattern within the matches_rule()

function, and search and replace within the apply_rule() function) stay with those functions, even after

you return from build_match_and_apply_functions(). That’s insanely cool.

If this is incredibly confusing (and it should be, this is weird stuff), it may become clearer when you see how

to use it.

160

patterns = \

(

('[sxz]$', '$', 'es'),

('[^aeioudgkprt]h$', '$', 'es'),

('(qu|[^aeiou])y$', 'y$', 'ies'),

('$', '$', 's')

)

rules = [build_match_and_apply_functions(pattern, search, replace)

for (pattern, search, replace) in patterns]

1. Our pluralization “rules” are now defined as a tuple of tuples of strings (not functions). The first string in

each group is the regular expression pattern that you would use in re.search() to see if this rule matches.

The second and third strings in each group are the search and replace expressions you would use in

re.sub() to actually apply the rule to turn a noun into its plural.

2. There’s a slight change here, in the fallback rule. In the previous example, the match_default() function

simply returned True, meaning that if none of the more specific rules matched, the code would simply add

an s to the end of the given word. This example does something functionally equivalent. The final regular

expression asks whether the word has an end ($ matches the end of a string). Of course, every string has

an end, even an empty string, so this expression always matches. Thus, it serves the same purpose as the

match_default() function that always returned True: it ensures that if no more specific rule matches, the

code adds an s to the end of the given word.

3. This line is magic. It takes the sequence of strings in patterns and turns them into a sequence of functions.

How? By “mapping” the strings to the build_match_and_apply_functions() function. That is, it takes each

triplet of strings and calls the build_match_and_apply_functions() function with those three strings as

arguments. The build_match_and_apply_functions() function returns a tuple of two functions. This means

that rules ends up being functionally equivalent to the previous example: a list of tuples, where each tuple is

a pair of functions. The first function is the match function that calls re.search(), and the second function

is the apply function that calls re.sub().

Rounding out this version of the script is the main entry point, the plural() function.

161

def plural(noun):

for matches_rule, apply_rule in rules:

if matches_rule(noun):

return apply_rule(noun)

1. Since the rules list is the same as the previous example (really, it is), it should come as no surprise that the

plural() function hasn’t changed at all. It’s completely generic; it takes a list of rule functions and calls them

in order. It doesn’t care how the rules are defined. In the previous example, they were defined as separate

named functions. Now they are built dynamically by mapping the output of the

build_match_and_apply_functions() function onto a list of raw strings. It doesn’t matter; the plural()

function still works the same way.

6.5. A FILE OF PATTERNS

You’ve factored out all the duplicate code and added enough abstractions so that the pluralization rules are

defined in a list of strings. The next logical step is to take these strings and put them in a separate file,

where they can be maintained separately from the code that uses them.

First, let’s create a text file that contains the rules you want. No fancy data structures, just whitespace-

delimited strings in three columns. Let’s call it plural4-rules.txt.

[sxz]$ $ es

[^aeioudgkprt]h$ $ es

[^aeiou]y$ y$ ies

$ $ s

Now let’s see how you can use this rules file.

162

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))

1. The build_match_and_apply_functions() function has not changed. You’re still using closures to build

two functions dynamically that use variables defined in the outer function.

2. The global open() function opens a file and returns a file object. In this case, the file we’re opening contains

the pattern strings for pluralizing nouns. The with statement creates what’s called a context: when the with

block ends, Python will automatically close the file, even if an exception is raised inside the with block.

You’ll learn more about with blocks and file objects in the Files chapter.

3. The for line in <fileobject> idiom reads data from the open file, one line at a time, and assigns the

text to the line variable. You’ll learn more about reading from files in the Files chapter.

4. Each line in the file really has three values, but they’re separated by whitespace (tabs or spaces, it makes no

difference). To split it out, use the split() string method. The first argument to the split() method is

None, which means “split on any whitespace (tabs or spaces, it makes no difference).” The second argument

is 3, which means “split on whitespace 3 times, then leave the rest of the line alone.” A line like [sxz]$ $

es will be broken up into the list ['[sxz]$', '$', 'es'], which means that pattern will get '[sxz]$',

search will get '$', and replace will get 'es'. That’s a lot of power in one little line of code.

5. Finally, you pass pattern, search, and replace to the build_match_and_apply_functions() function,

which returns a tuple of functions. You append this tuple to the rules list, and rules ends up storing the

list of match and apply functions that the plural() function expects.

163

The improvement here is that you’ve completely separated the pluralization rules into an external file, so it

can be maintained separately from the code that uses it. Code is code, data is data, and life is good.

6.6. 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.

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.

164

>>> 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

1. 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.

2. 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.

3. The make_counter() function returns a generator object.

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

the counter generator, it executes the code in make_counter() up to the first yield statement, then

returns the value that was yielded. In this case, that will be 2, because you originally created the generator

by calling make_counter(2).

5. Repeatedly calling next() with the same generator object resumes exactly where it left off and continues

until it hits the next yield statement. All variables, local state, & c. are saved on yield and restored on

next(). The next line of code waiting to be executed calls print(), which prints incrementing x. After

165

that, the statement x = x + 1. Then it loops through the while loop again, and the first thing it hits is the

statement yield x, which saves the state of everything and returns the current value of x (now 3).

6. The second time you call next(counter), you do all the same things again, but this time x is now 4.

Since make_counter sets up an infinite loop, you could theoretically do this forever, and it would just keep

incrementing x and spitting out values. But let’s look at more productive uses of generators instead.

6.6.1. A FIBONACCI GENERATOR

def fib(max):

a, b = 0, 1

while a < max:

yield a

a, b = b, a + b

“yield”

1. The Fibonacci sequence is a sequence of numbers where

pauses a

each number is the sum of the two numbers before it.

It starts with 0 and 1, goes up slowly at first, then more

function.

and more rapidly. To start the sequence, you need two

variables: a starts at 0, and b starts at 1.

“next()”

2. a is the current number in the sequence, so yield it.

3. b is the next number in the sequence, so assign that to

resumes

a, but also calculate the next value (a + b) and assign

that to b for later use. Note that this happens in

where it left

parallel; if a is 3 and b is 5, then a, b = b, a + b will

set a to 5 (the previous value of b) and b to 8 (the sum

off.

of the previous values of a and b).

So you have a function that spits out successive

Fibonacci numbers. Sure, you could do that with

recursion, but this way is easier to read. Also, it works well with for loops.

166

>>> from fibonacci import fib

>>> for n in fib(1000):

...

print(n, end=' ')

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

>>> list(fib(1000))

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]

1. You can use a generator like fib() in a for loop directly. The for loop will automatically call the next()

function to get values from the fib() generator and assign them to the for loop index variable (n).

2. Each time through the for loop, n gets a new value from the yield statement in fib(), and all you have to

do is print it out. Once fib() runs out of numbers (a becomes bigger than max, which in this case is 1000),

then the for loop exits gracefully.

3. This is a useful idiom: pass a generator to the list() function, and it will iterate through the entire

generator (just like the for loop in the previous example) and return a list of all the values.

6.6.2. A PLURAL RULE GENERATOR

Let’s go back to plural5.py and see how this version of the plural() function works.

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))

1. No magic here. Remember that the lines of the rules file have three values separated by whitespace, so you

use line.split(None, 3) to get the three “columns” and assign them to three local variables.

167

2. And then you yield. What do you yield? Two functions, built dynamically with your old friend,

build_match_and_apply_functions(), which is identical to the previous examples. In other words,

rules() is a generator that spits out match and apply functions on demand.

3. Since rules() is a generator, you can use it directly in a for loop. The first time through the for loop, you

will call the rules() function, which will open the pattern file, read the first line, dynamically build a match

function and an apply function from the patterns on that line, and yield the dynamically built functions. The

second time through the for loop, you will pick up exactly where you left off in rules() (which was in the

middle of the for line in pattern_file loop). The first thing it will do is read the next line of the file

(which is still open), dynamically build another match and apply function based on the patterns on that line in

the file, and yield the two functions.

What have you gained over stage 4? Startup time. In stage 4, when you imported the plural4 module, it

read the entire patterns file and built a list of all the possible rules, before you could even think about calling

the plural() function. With generators, you can do everything lazily: you read the first rule and create

functions and try them, and if that works you don’t ever read the rest of the file or create any other

functions.

What have you lost? Performance! Every time you call the plural() function, the rules() generator starts

over from the beginning — which means re-opening the patterns file and reading from the beginning, one line

at a time.

What if you could have the best of both worlds: minimal startup cost (don’t execute any code on import),

and maximum performance (don’t build the same functions over and over again). Oh, and you still want to

keep the rules in a separate file (because code is code and data is data), just as long as you never have to

read the same line twice.

To do that, you’ll need to build your own iterator. But before you do that, you need to learn about Python

classes.

168

6.7. FURTHER READING

PEP 255: Simple Generators

Understanding Python’s “with” statement

Closures in Python

Fibonacci numbers

English Irregular Plural Nouns

169

CHAPTER 7. CLASSES & ITERATORS

East is East, and West is West, and never the twain shall meet.

Rudyard Kipling

7.1. DIVING IN

Iteratorsarethe“secretsauce”ofPython3.They’reeverywhere,underlyingeverything,alwaysjustout

of sight. Comprehensions are just a simple form of iterators. Generators are just a simple form of iterators. A function that yields values is a nice, compact way of building an iterator without building an iterator. Let me

show you what I mean by that.

Remember the Fibonacci generator? Here it is as a built-from-scratch iterator:

170

class Fib:

'''iterator that yields numbers in the Fibonacci sequence'''

def __init__(self, max):

self.max = max

def __iter__(self):

self.a = 0

self.b = 1

return self

def __next__(self):

fib = self.a

if fib > self.max:

raise StopIteration

self.a, self.b = self.b, self.a + self.b

return fib

Let’s take that one line at a time.

class Fib:

class? What’s a class?

7.2. DEFINING CLASSES

Python is fully object-oriented: you can define your own classes, inherit from your own or built-in classes,

and instantiate the classes you’ve defined.

171

Defining a class in Python is simple. As with functions, there is no separate interface definition. Just define

the class and start coding. A Python class starts with the reserved word class, followed by the class name.

Technically, that’s all that’s required, since a class doesn’t need to inherit from any other class.

class PapayaWhip:

pass

1. The name of this class is PapayaWhip, and it doesn’t inherit from any other class. Class names are usually

capitalized, EachWordLikeThis, but this is only a convention, not a requirement.

2. You probably guessed this, but everything in a class is indented, just like the code within a function, if

statement, for loop, or any other block of code. The first line not indented is outside the class.

This PapayaWhip class doesn’t define any methods or attributes, but syntactically, there needs to be

something in the definition, thus the pass statement. This is a Python reserved word that just means “move

along, nothing to see here”. It’s a statement that does nothing, and it’s a good placeholder when you’re

stubbing out functions or classes.

☞ The pass statement in Python is like a empty set of curly braces ({}) in Java or C.

Many classes are inherited from other classes, but this one is not. Many classes define methods, but this one

does not. There is nothing that a Python class absolutely must have, other than a name. In particular, C++

programmers may find it odd that Python classes don’t have explicit constructors and destructors. Although

it’s not required, Python classes can have something similar to a constructor: the __init__() method.

7.2.1. THE __init__() METHOD

This example shows the initialization of the Fib class using the __init__ method.

class Fib:

'''iterator that yields numbers in the Fibonacci sequence'''

def __init__(self, max):

172

1. Classes can (and should) have docstrings too, just like modules and functions.

2. The __init__() method is called immediately after an instance of the class is created. It would be

tempting — but technically incorrect — to call this the “constructor” of the class. It’s tempting, because it

looks like a C++ constructor (by convention, the __init__() method is the first method defined for the

class), acts like one (it’s the first piece of code executed in a newly created instance of the class), and even

sounds like one. Incorrect, because the object has already been constructed by the time the __init__()

method is called, and you already have a valid reference to the new instance of the class.

The first argument of every class method, including the __init__() method, is always a reference to the

current instance of the class. By convention, this argument is named self. This argument fills the role of the

reserved word this in C++ or Java, but self is not a reserved word in Python, merely a naming

convention. Nonetheless, please don’t call it anything but self; this is a very strong convention.

In the __init__() method, self refers to the newly created object; in other class methods, it refers to the

instance whose method was called. Although you need to specify self explicitly when defining the method,

you do not specify it when calling the method; Python will add it for you automatically.

7.3. INSTANTIATING CLASSES

Instantiating classes in Python is straightforward. To instantiate a class, simply call the class as if it were a

function, passing the arguments that the __init__() method requires. The return value will be the newly

created object.

173

>>> import fibonacci2

>>> fib = fibonacci2.Fib(100)

>>> fib

<fibonacci2.Fib object at 0x00DB8810>

>>> fib.__class__

<class 'fibonacci2.Fib'>

>>> fib.__doc__

'iterator that yields numbers in the Fibonacci sequence'

1. You are creating an instance of the Fib class (defined in the fibonacci2 module) and assigning the newly

created instance to the variable fib. You are passing one parameter, 100, which will end up as the max

argument in Fib’s __init__() method.

2. fib is now an instance of the Fib class.

3. Every class instance has a built-in attribute, __class__, which is the object’s class. Java programmers may be

familiar with the Class class, which contains methods like getName() and getSuperclass() to get metadata

information about an object. In Python, this kind of metadata is available through attributes, but the idea is

the same.

4. You can access the instance’s docstring just as with a function or a module. All instances of a class share

the same docstring.

☞ In Python, simply call a class as if it were a function to create a new instance of the

class. There is no explicit new operator like there is in C++ or Java.

7.4. INSTANCE VARIABLES

On to the next line:

174

class Fib:

def __init__(self, max):

self.max = max

1. What is self.max? It’s an instance variable. It is completely separate from max, which was passed into the

__init__() method as an argument. self.max is “global” to the instance. That means that you can access it

from other methods.

class Fib:

def __init__(self, max):

self.max = max

.

.

.

def __next__(self):

fib = self.a

if fib > self.max:

1. self.max is defined in the __init__() method…

2. …and referenced in the __next__() method.

Instance variables are specific to one instance of a class. For example, if you create two Fib instances with

different maximum values, they will each remember their own values.

>>> import fibonacci2

>>> fib1 = fibonacci2.Fib(100)

>>> fib2 = fibonacci2.Fib(200)

>>> fib1.max

100

>>> fib2.max

200

175

7.5. A FIBONACCI ITERATOR

Now you’re ready to learn how to build an iterator. An iterator is just a class that defines an __iter__()

method.

class Fib:

def __init__(self, max):

All three of these class

self.max = max

methods, __init__,

__iter__, and

def __iter__(self):

__next__, begin and

self.a = 0

end with a pair of

self.b = 1

underscore (_)

return self

characters. Why is that?

There’s nothing magical

def __next__(self):

about it, but it usually

fib = self.a

indicates that these are

if fib > self.max:

“special methods.” The

raise StopIteration

only thing “special”

self.a, self.b = self.b, self.a + self.b

about special methods is

return fib

that they aren’t called

directly; Python calls

them when you use some

1. To build an iterator from scratch, Fib needs to be a class, not a

other syntax on the class

function.

or an instance of the

2. “Calling” Fib(max) is really creating an instance of this class and

class. More about special

calling its __init__() method with max. The __init__() method

methods.

saves the maximum value as an instance variable so other methods

can refer to it later.

3. The __iter__() method is called whenever someone calls

iter(fib). (As you’ll see in a minute, a for loop will call this automatically, but you can also call it yourself

manually.) After performing beginning-of-iteration initialization (in this case, resetting self.a and self.b, our

two counters), the __iter__() method can return any object that implements a __next__() method. In this

case (and in most cases), __iter__() simply returns self, since this class implements its own __next__()

method.

176

4. The __next__() method is called whenever someone calls next() on an iterator of an instance of a class.

That will make more sense in a minute.

5. When the __next__() method raises a StopIteration exception, this signals to the caller that the iteration

is exhausted. Unlike most exceptions, this is not an error; it’s a normal condition that just means that the

iterator has no more values to generate. If the caller is a for loop, it will notice this StopIteration

exception and gracefully exit the loop. (In other words, it will swallow the exception.) This little bit of magic

is actually the key to using iterators in for loops.

6. To spit out the next value, an iterator’s __next__() method simply returns the value. Do not use yield

here; that’s a bit of syntactic sugar that only applies when you’re using generators. Here you’re creating your

own iterator from scratch; use return instead.

Thoroughly confused yet? Excellent. Let’s see how to call this iterator:

>>> from fibonacci2 import Fib

>>> for n in Fib(1000):

...

print(n, end=' ')

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

Why, it’s exactly the same! Byte for byte identical to how you called Fibonacci-as-a-generator (modulo one capital letter). But how?

There’s a bit of magic involved in for loops. Here’s what happens:

• The for loop calls Fib(1000), as shown. This returns an instance of the Fib class. Call this fib_inst.

• Secretly, and quite cleverly, the for loop calls iter(fib_inst), which returns an iterator object. Call this

fib_iter. In this case, fib_iter == fib_inst, because the __iter__() method returns self, but the for

loop doesn’t know (or care) about that.

• To “loop through” the iterator, the for loop calls next(fib_iter), which calls the __next__() method on

the fib_iter object, which does the next-Fibonacci-number calculations and returns a value. The for loop

takes this value and assigns it to n, then executes the body of the for loop for that value of n.

• How does the for loop know when to stop? I’m glad you asked! When next(fib_iter) raises a

StopIteration exception, the for loop will swallow the exception and gracefully exit. (Any other exception

will pass through and be raised as usual.) And where have you seen a StopIteration exception? In the

__next__() method, of course!

177

7.6. A PLURAL RULE ITERATOR

Now it’s time for the finale. Let’s rewrite the plural

rules generator as an iterator.

iter(f) calls

f.__iter__

next(f) calls

f.__next__

178

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.

179

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

class LazyRules:

rules_filename = 'plural6-rules.txt'

def __init__(self):

self.pattern_file = open(self.rules_filename, encoding='utf-8')

self.cache = []

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

later.)

2. 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.

180

>>> import plural6

>>> r1 = plural6.LazyRules()

>>> r2 = plural6.LazyRules()

>>> r1.rules_filename

'plural6-rules.txt'

>>> r2.rules_filename

'plural6-rules.txt'

>>> r2.rules_filename = 'r2-override.txt'

>>> r2.rules_filename

'r2-override.txt'

>>> r1.rules_filename

'plural6-rules.txt'

>>> r2.__class__.rules_filename

'plural6-rules.txt'

>>> r2.__class__.rules_filename = 'papayawhip.txt'

>>> r1.rules_filename

'papayawhip.txt'

>>> r2.rules_filename

'r2-overridetxt'

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

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

3. …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.

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

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

And now back to our show.

def __iter__(self):

self.cache_index = 0

return self

1. The __iter__() method will be called every time someone — say, a for loop — calls iter(rules).

181

2. The one thing that every __iter__() method must do is return an iterator. In this case, it returns self,

which signals that this class defines a __next__() method which will take care of returning values

throughout the iteration.

def __next__(self):

.

.

.

pattern, search, replace = line.split(None, 3)

funcs = build_match_and_apply_functions(

pattern, search, replace)

self.cache.append(funcs)

return funcs

1. The __next__() method gets called whenever someone — say, a for loop — calls next(rules). This

method will only make sense if we start at the end and work backwards. So let’s do that.

2. The last part of this function should look familiar, at least. The build_match_and_apply_functions()

function hasn’t changed; it’s the same as it ever was.

3. The only difference is that, before returning the match and apply functions (which are stored in the tuple

funcs), we’re going to save them in self.cache.

Moving backwards…

def __next__(self):

.

.

.

line = self.pattern_file.readline()

if not line:

self.pattern_file.close()

raise StopIteration

.

.

.

182

1. A bit of advanced file trickery here. The readline() method (note: singular, not the plural readlines())

reads exactly one line from an open file. Specifically, the next line. ( File objects are iterators too! It’s iterators all

the way down…)

2. If there was a line for readline() to read, line will not be an empty string. Even if the file contained a

blank line, line would end up as the one-character string '\n' (a carriage return). If line is really an empty

string, that means there are no more lines to read from the file.

3. When we reach the end of the file, we should close the file and raise the magic StopIteration exception.

Remember, we got to this point because we needed a match and apply function for the next rule. The next

rule comes from the next line of the file… but there is no next line! Therefore, we have no value to return.

The iteration is over. (♫ The party’s over… ♫)

Moving backwards all the way to the start of the __next__() method…

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

.

.

.

1. self.cache will be a list of the functions we need to match and apply individual rules. (At least that should

sound familiar!) self.cache_index keeps track of which cached item we should return next. If we haven’t

exhausted the cache yet ( i.e. if the length of self.cache is greater than self.cache_index), then we have a

cache hit! Hooray! We can return the match and apply functions from the cache instead of building them

from scratch.

2. On the other hand, if we don’t get a hit from the cache, and the file object has been closed (which could

happen, further down the method, as you saw in the previous code snippet), then there’s nothing more we

can do. If the file is closed, it means we’ve exhausted it — we’ve already read through every line from the

pattern file, and we’ve already built and cached the match and apply functions for each pattern. The file is

exhausted; the cache is exhausted; I’m exhausted. Wait, what? Hang in there, we’re almost done.

183

Putting it all together, here’s what happens when:

• When the module is imported, it creates a single instance of the LazyRules class, called rules, which opens

the pattern file but does not read from it.

• When asked for the first match and apply function, it checks its cache but finds the cache is empty. So it

reads a single line from the pattern file, builds the match and apply functions from those patterns, and caches

them.

• Let’s say, for the sake of argument, that the very first rule matched. If so, no further match and apply

functions are built, and no further lines are read from the pattern file.

• Furthermore, for the sake of argument, suppose that the caller calls the plural() function again to pluralize

a different word. The for loop in the plural() function will call iter(rules), which will reset the cache

index but will not reset the open file object.

• The first time through, the for loop will ask for a value from rules, which will invoke its __next__()

method. This time, however, the cache is primed with a single pair of match and apply functions,

corresponding to the patterns in the first line of the pattern file. Since they were built and cached in the

course of pluralizing the previous word, they’re retrieved from the cache. The cache index increments, and

the open file is never touched.

• Let’s say, for the sake of argument, that the first rule does not match this time around. So the for loop

comes around again and asks for another value from rules. This invokes the __next__() method a second

time. This time, the cache is exhausted — it only contained one item, and we’re asking for a second — so

the __next__() method continues. It reads another line from the open file, builds match and apply functions

out of the patterns, and caches them.

• This read-build-and-cache process will continue as long as the rules being read from the pattern file don’t

match the word we’re trying to pluralize. If we do find a matching rule before the end of the file, we simply

use it and stop, with the file still open. The file pointer will stay wherever we stopped reading, waiting for

the next readline() command. In the meantime, the cache now has more items in it, and if we start all

over again trying to pluralize a new word, each of those items in the cache will be tried before reading the

next line from the pattern file.

We have achieved pluralization nirvana.

1. Minimal startup cost. The only thing that happens on import is instantiating a single class and opening a

file (but not reading from it).

184

2. Maximum performance. The previous example would read through the file and build functions

dynamically every time you wanted to pluralize a word. This version will cache functions as soon as they’re

built, and in the worst case, it will only read through the pattern file once, no matter how many words you

pluralize.

3. Separation of code and data. All the patterns are stored in a separate file. Code is code, and data is

data, and never the twain shall meet.

☞ Is this really nirvana? Well, yes and no. Here’s something to consider with the

LazyRules example: the pattern file is opened (during __init__()), and it remains

open until the final rule is reached. Python will eventually close the file when it exits,

or after the last instantiation of the LazyRules class is destroyed, but still, that could

be a long time. If this class is part of a long-running Python process, the Python

interpreter may never exit, and the LazyRules object may never get destroyed.

There are ways around this. Instead of opening the file during __init__() and

leaving it open while you read rules one line at a time, you could open the file, read

all the rules, and immediately close the file. Or you could open the file, read one

rule, save the file position with the tell() method, close the file, and later re-open

it and use the seek() method to continue reading where you left off. Or you could

not worry about it and just leave the file open, like this example code does.

Programming is design, and design is all about trade-offs and constraints. Leaving a file

open too long might be a problem; making your code more complicated might be a

problem. Which one is the bigger problem depends on your development team, your

application, and your runtime environment.

7.7. FURTHER READING

Iterator types

PEP 234: Iterators

185

PEP 255: Simple Generators

Generator Tricks for Systems Programmers

186

CHAPTER 8. ADVANCED ITERATORS

Great fleas have little fleas upon their backs to bite ’em,

And little fleas have lesser fleas, and so ad infinitum.

— Augustus De Morgan

8.1. DIVING IN

Justasregularexpressionsputstringsonsteroids,theitertoolsmoduleputsiteratorsonsteroids.But first, I want to show you a classic puzzle.

HAWAII + IDAHO + IOWA + OHIO == STATES

510199 + 98153 + 9301 + 3593 == 621246

H = 5

A = 1

W = 0

I = 9

D = 8

O = 3

S = 6

T = 2

E = 4

Puzzles like this are called cryptarithms or alphametics. The letters spell out actual words, but if you replace each letter with a digit from 0–9, it also “spells” an arithmetic equation. The trick is to figure out which

letter maps to each digit. All the occurrences of each letter must map to the same digit, no digit can be

repeated, and no “word” can start with the digit 0.

187

In this chapter, we’ll dive into an incredible Python

program originally written by Raymond Hettinger. This

program solves alphametic puzzles in just 14 lines of code.

The most

well-known

alphametic

puzzle is

SEND +

MORE =

MONEY .

188

import re

import itertools

def solve(puzzle):

words = re.findall('[A-Z]+', puzzle.upper())

unique_characters = set(''.join(words))

assert len(unique_characters) <= 10, 'Too many letters'

first_letters = {word[0] for word in words}

n = len(first_letters)

sorted_characters = ''.join(first_letters) + \

''.join(unique_characters - first_letters)

characters = tuple(ord(c) for c in sorted_characters)

digits = tuple(ord(c) for c in '0123456789')

zero = digits[0]

for guess in itertools.permutations(digits, len(characters)):

if zero not in guess[:n]:

equation = puzzle.translate(dict(zip(characters, guess)))

if eval(equation):

return equation

if __name__ == '__main__':

import sys

for puzzle in sys.argv[1:]:

print(puzzle)

solution = solve(puzzle)

if solution:

print(solution)

You can run the program from the command line. On Linux, it would look like this. (These may take some

time, depending on the speed of your computer, and there is no progress bar. Just be patient!)

189

you@localhost:~/diveintopython3/examples$ python3 alphametics.py "HAWAII + IDAHO + IOWA + OHIO == STATES"

HAWAII + IDAHO + IOWA + OHIO = STATES

510199 + 98153 + 9301 + 3593 == 621246

you@localhost:~/diveintopython3/examples$ python3 alphametics.py "I + LOVE + YOU == DORA"

I + LOVE + YOU == DORA

1 + 2784 + 975 == 3760

you@localhost:~/diveintopython3/examples$ python3 alphametics.py "SEND + MORE == MONEY"

SEND + MORE == MONEY

9567 + 1085 == 10652

8.2. FINDING ALL OCCURRENCES OF A PATTERN

The first thing this alphametics solver does is find all the letters (A–Z) in the puzzle.

>>> import re

>>> re.findall('[0-9]+', '16 2-by-4s in rows of 8')

['16', '2', '4', '8']

>>> re.findall('[A-Z]+', 'SEND + MORE == MONEY')

['SEND', 'MORE', 'MONEY']

1. The re module is Python’s implementation of regular expressions. It has a nifty function called findall() which takes a regular expression pattern and a string, and finds all occurrences of the pattern within the

string. In this case, the pattern matches sequences of numbers. The findall() function returns a list of all

the substrings that matched the pattern.

2. Here the regular expression pattern matches sequences of letters. Again, the return value is a list, and each

item in the list is a string that matched the regular expression pattern.

Here’s another example that will stretch your brain a little.

190

>>> re.findall(' s.*? s', "The sixth sick sheikh's sixth sheep's sick.")

[' sixth s', " sheikh's s", " sheep's s"]

Surprised? The regular expression looks for a space, an

s, and then the shortest possible series of any character

(.*?), then a space, then another s. Well, looking at

that input string, I see five matches:

This is the

1. The sixth sick sheikh's sixth sheep's sick.

2. The sixth sick sheikh's sixth sheep's sick.

hardest

3. The sixth sick sheikh's sixth sheep's sick.

4. The sixth sick sheikh's sixth sheep's sick.

tongue

5. The sixth sick sheikh's sixth sheep's sick.

twister in

But the re.findall() function only returned three

matches. Specifically, it returned the first, the third, and

the English

the fifth. Why is that? Because it doesn’t return

overlapping matches. The first match overlaps with the

language.

second, so the first is returned and the second is

skipped. Then the third overlaps with the fourth, so the

third is returned and the fourth is skipped. Finally, the

fifth is returned. Three matches, not five.

This has nothing to do with the alphametics solver; I just thought it was interesting.

8.3. FINDING THE UNIQUE ITEMS IN A SEQUENCE

Sets make it trivial to find the unique items in a sequence.

191

>>> a_list = ['The', 'sixth', 'sick', "sheik's", 'sixth', "sheep's", 'sick']

>>> set(a_list)

{'sixth', 'The', "sheep's", 'sick', "sheik's"}

>>> a_string = 'EAST IS EAST'

>>> set(a_string)

{'A', ' ', 'E', 'I', 'S', 'T'}

>>> words = ['SEND', 'MORE', 'MONEY']

>>> ''.join(words)

'SENDMOREMONEY'

>>> set(''.join(words))

{'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}

1. Given a list of several strings, the set() function will return a set of unique strings from the list. This makes

sense if you think of it like a for loop. Take the first item from the list, put it in the set. Second. Third.

Fourth. Fifth — wait, that’s in the set already, so it only gets listed once, because Python sets don’t allow

duplicates. Sixth. Seventh — again, a duplicate, so it only gets listed once. The end result? All the unique

items in the original list, without any duplicates. The original list doesn’t even need to be sorted first.

2. The same technique works with strings, since a string is just a sequence of characters.

3. Given a list of strings, ''.join(a_list) concatenates all the strings together into one.

4. So, given a list of strings, this line of code returns all the unique characters across all the strings, with no

duplicates.

The alphametics solver uses this technique to build a set of all the unique characters in the puzzle.

unique_characters = set(''.join(words))

This list is later used to assign digits to characters as the solver iterates through the possible solutions.

192

8.4. MAKING ASSERTIONS

Like many programming languages, Python has an assert statement. Here’s how it works.

>>> assert 1 + 1 == 2

>>> assert 1 + 1 == 3

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AssertionError

>>> assert 2 + 2 == 5, "Only for very large values of 2"

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AssertionError: Only for very large values of 2

1. The assert statement is followed by any valid Python expression. In this case, the expression 1 + 1 == 2

evaluates to True, so the assert statement does nothing.

2. However, if the Python expression evaluates to False, the assert statement will raise an AssertionError.

3. You can also include a human-readable message that is printed if the AssertionError is raised.

Therefore, this line of code:

assert len(unique_characters) <= 10, 'Too many letters'

…is equivalent to this:

if len(unique_characters) > 10:

raise AssertionError('Too many letters')

The alphametics solver uses this exact assert statement to bail out early if the puzzle contains more than

ten unique letters. Since each letter is assigned a unique digit, and there are only ten digits, a puzzle with

more than ten unique letters can not possibly have a solution.

193

8.5. GENERATOR EXPRESSIONS

A generator expression is like a generator function without the function.

>>> unique_characters = {'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}

>>> gen = (ord(c) for c in unique_characters)

>>> gen

<generator object <genexpr> at 0x00BADC10>

>>> next(gen)

69

>>> next(gen)

68

>>> tuple(ord(c) for c in unique_characters)

(69, 68, 77, 79, 78, 83, 82, 89)

1. A generator expression is like an anonymous function that yields values. The expression itself looks like a list

comprehension, but it’s wrapped in parentheses instead of square brackets.

2. The generator expression returns… an iterator.

3. Calling next(gen) returns the next value from the iterator.

4. If you like, you can iterate through all the possible values and return a tuple, list, or set, by passing the

generator expression to tuple(), list(), or set(). In these cases, you don’t need an extra set of

parentheses — just pass the “bare” expression ord(c) for c in unique_characters to the tuple()

function, and Python figures out that it’s a generator expression.

☞ Using a generator expression instead of a list comprehension can save both CPU and

R A M . If you’re building an list just to throw it away ( e.g. passing it to tuple() or

set()), use a generator expression instead!

Here’s another way to accomplish the same thing, using a generator function:

194

def ord_map(a_string):

for c in a_string:

yield ord(c)

gen = ord_map(unique_characters)

The generator expression is more compact but functionally equivalent.

8.6. CALCULATING PERMUTATIONS… THE LAZY WAY!

First of all, what the heck are permutations? Permutations are a mathematical concept. (There are actually

several definitions, depending on what kind of math you’re doing. Here I’m talking about combinatorics, but if

that doesn’t mean anything to you, don’t worry about it. As always, Wikipedia is your friend.)

The idea is that you take a list of things (could be numbers, could be letters, could be dancing bears) and

find all the possible ways to split them up into smaller lists. All the smaller lists have the same size, which

can be as small as 1 and as large as the total number of items. Oh, and nothing can be repeated.

Mathematicians say things like “let’s find the permutations of 3 different items taken 2 at a time,” which

means you have a sequence of 3 items and you want to find all the possible ordered pairs.

195

>>> import itertools

>>> perms = itertools.permutations([1, 2, 3], 2)

>>> next(perms)

(1, 2)

>>> next(perms)

(1, 3)

>>> next(perms)

(2, 1)

>>> next(perms)

(2, 3)

>>> next(perms)

(3, 1)

>>> next(perms)

(3, 2)

>>> next(perms)

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

StopIteration

1. The itertools module has all kinds of fun stuff in it, including a permutations() function that does all the

hard work of finding permutations.

2. The permutations() function takes a sequence (here a list of three integers) and a number, which is the

number of items you want in each smaller group. The function returns an iterator, which you can use in a

for loop or any old place that iterates. Here I’ll step through the iterator manually to show all the values.

3. The first permutation of [1, 2, 3] taken 2 at a time is (1, 2).

4. Note that permutations are ordered: (2, 1) is different than (1, 2).

5. That’s it! Those are all the permutations of [1, 2, 3] taken 2 at a time. Pairs like (1, 1) and (2, 2)

never show up, because they contain repeats so they aren’t valid permutations. When there are no more

permutations, the iterator raises a StopIteration exception.

196

The permutations() function doesn’t have to take a

list. It can take any sequence — even a string.

The

itertools

module has

all kinds of

fun stuff.

197

>>> import itertools

>>> perms = itertools.permutations('ABC', 3)

>>> next(perms)

('A', 'B', 'C')

>>> next(perms)

('A', 'C', 'B')

>>> next(perms)

('B', 'A', 'C')

>>> next(perms)

('B', 'C', 'A')

>>> next(perms)

('C', 'A', 'B')

>>> next(perms)

('C', 'B', 'A')

>>> next(perms)

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

StopIteration