wrote advanced-unit-testing chapter, decided to merge it into unit-testing. renumbered chapters and fixed up TOC and navigation

This commit is contained in:
Mark Pilgrim
2009-07-25 15:31:55 -04:00
parent 71821cfadc
commit e5b43fb442
19 changed files with 187 additions and 1933 deletions
-242
View File
@@ -1,242 +0,0 @@
<!DOCTYPE html>
<head>
<meta charset=utf-8>
<title>Advanced Classes - Dive into Python 3</title>
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 11}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
<meta name=viewport content='initial-scale=1.0'>
</head>
<form action=http://www.google.com/cse><div><input type=hidden name=cx value=014021643941856155761:l5eihuescdw><input type=hidden name=ie value=UTF-8>&nbsp;<input name=q size=25>&nbsp;<input type=submit name=sa value=Search></div></form>
<p>You are here: <a href=index.html>Home</a> <span class=u>&#8227;</span> <a href=table-of-contents.html#advanced-classes>Dive Into Python 3</a> <span class=u>&#8227;</span>
<p id=level>Difficulty level: <span class=u title=advanced>&#x2666;&#x2666;&#x2666;&#x2666;&#x2662;</span>
<h1>Advanced Classes</h1>
<blockquote class=q>
<p><span class=u>&#x275D;</span> FIXME <span class=u>&#x275E;</span><br>&mdash; FIXME
</blockquote>
<p id=toc>&nbsp;
<h2 id=divingin>Diving In</h2>
<p class=f>FIXME
<h2 id=ordereddict>Ordered Dictionary: Not An Oxymoron</h2>
<p>[FIXME here's why ordered dicts are useful: http://www.gossamer-threads.com/lists/python/dev/656556 ]
<p class=d>[<a href=examples/ordereddict.py>download <code>ordereddict.py</code></a>]
<pre><code class=pp>class OrderedDict(dict, MutableMapping):
'Dictionary that remembers insertion order'
# An inherited dict maps keys to values.
# The inherited dict provides __getitem__, __len__, __contains__, and get.
# The remaining methods are order-aware.
# Big-O running times for all methods are the same as for regular dictionaries.
# The internal self.__map dictionary maps keys to links in a doubly linked list.
# The circular doubly linked list starts and ends with a sentinel element.
# The sentinel element never gets deleted (this simplifies the algorithm).
# The prev/next links are weakref proxies (to prevent circular references).
# Individual links are kept alive by the hard reference in self.__map.
# Those hard references disappear when a key is deleted from an OrderedDict.
def __init__(self, *args, **kwds):
'''Initialize an ordered dictionary. Signature is the same as for
regular dictionaries, but keyword arguments are not recommended
because their insertion order is arbitrary.
'''
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__root
except AttributeError:
self.__root = root = _Link() # sentinel node for the doubly linked list
root.prev = root.next = root
self.__map = {}
self.update(*args, **kwds)
def clear(self):
'od.clear() -> None. Remove all items from od.'
root = self.__root
root.prev = root.next = root
self.__map.clear()
dict.clear(self)
def __setitem__(self, key, value):
'od.__setitem__(i, y) <==> od[i]=y'
# Setting a new item creates a new link which goes at the end of the linked
# list, and the inherited dictionary is updated with the new key/value pair.
if key not in self:
self.__map[key] = link = _Link()
root = self.__root
last = root.prev
link.prev, link.next, link.key = last, root, key
last.next = root.prev = _proxy(link)
dict.__setitem__(self, key, value)
def __delitem__(self, key):
'od.__delitem__(y) <==> del od[y]'
# Deleting an existing item uses self.__map to find the link which is
# then removed by updating the links in the predecessor and successor nodes.
dict.__delitem__(self, key)
link = self.__map.pop(key)
link.prev.next = link.next
link.next.prev = link.prev
def __iter__(self):
'od.__iter__() <==> iter(od)'
# Traverse the linked list in order.
root = self.__root
curr = root.next
while curr is not root:
yield curr.key
curr = curr.next
def __reversed__(self):
'od.__reversed__() <==> reversed(od)'
# Traverse the linked list in reverse order.
root = self.__root
curr = root.prev
while curr is not root:
yield curr.key
curr = curr.prev
def __reduce__(self):
'Return state information for pickling'
items = [[k, self[k]] for k in self]
tmp = self.__map, self.__root
del self.__map, self.__root
inst_dict = vars(self).copy()
self.__map, self.__root = tmp
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
setdefault = MutableMapping.setdefault
update = MutableMapping.update
pop = MutableMapping.pop
keys = MutableMapping.keys
values = MutableMapping.values
items = MutableMapping.items
def popitem(self, last=True):
'''od.popitem() -> (k, v), return and remove a (key, value) pair.
Pairs are returned in LIFO order if last is true or FIFO order if false.
'''
if not self:
raise KeyError('dictionary is empty')
key = next(reversed(self) if last else iter(self))
value = self.pop(key)
return key, value
def __repr__(self):
'od.__repr__() <==> repr(od)'
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self.items()))
def copy(self):
'od.copy() -> a shallow copy of od'
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
'''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
and values equal to v (which defaults to None).
'''
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
'''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
while comparison to a regular mapping is order-insensitive.
'''
if isinstance(other, OrderedDict):
return len(self)==len(other) and \
all(p==q for p, q in zip(self.items(), other.items()))
return dict.__eq__(self, other)
def __ne__(self, other):
'''od.__ne__(y) <==> od!=y. Comparison to another OD is order-sensitive
while comparison to a regular mapping is order-insensitive.
'''
return not self == other</code></pre>
<p class=a>&#x2042;
<h2 id=class-attributes>Attributes of a Class Object</h2>
<p>FIXME
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>import ordereddict</kbd>
<samp class=p>>>> </samp><kbd class=pp>od = ordereddict.OrderedDict()</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>klass = od.__class__</kbd> <span class=u>&#x2460;</span></a>
<samp class=p>>>> </samp><kbd class=pp>type(klass)</kbd>
<samp class=pp>&lt;class 'abc.ABCMeta'></samp>
<samp class=p>>>> </samp><kbd class=pp>klass.__name__</kbd>
<samp class=pp>'OrderedDict'</samp>
<!--
<samp class=p>>>> </samp><kbd class=pp>klass.__doc__</kbd>
<samp class=pp>FIXME</samp>
-->
<samp class=p>>>> </samp><kbd class=pp>klass.__module__</kbd>
<samp class=pp>'ordereddict'</samp>
<samp class=p>>>> </samp><kbd class=pp>klass.__bases__</kbd>
<samp class=pp>(&lt;class 'dict'>, &lt;class '_abcoll.MutableMapping'>)</samp></pre>
<ol>
<li>FIXME
</ol>
<pre class=screen>
# continued from previous example
<samp class=p>>>> </samp><kbd class=pp>klass.__dict__</kbd>
<samp class=pp>{'__abstractmethods__': frozenset(),
'__delitem__': &lt;function __delitem__ at 0x00DCB6A8>,
'__dict__': &lt;attribute '__dict__' of 'OrderedDict' objects>,
'__doc__': None,
'__eq__': &lt;function __eq__ at 0x00DD2930>,
'__hash__': None,
'__init__': &lt;function __init__ at 0x00DC41E0>,
'__iter__': &lt;function __iter__ at 0x00DCB618>,
'__module__': 'ordereddict',
'__reduce__': &lt;function __reduce__ at 0x00DCB6F0>,
'__repr__': &lt;function __repr__ at 0x00DCB8E8>,
'__reversed__': &lt;function __reversed__ at 0x00DCB660>,
'__setitem__': &lt;function __setitem__ at 0x00DCB5D0>,
'__weakref__': &lt;attribute '__weakref__' of 'OrderedDict' objects>,
'_abc_cache': &lt;_weakrefset.WeakSet object at 0x00DCF950>,
'_abc_negative_cache': &lt;_weakrefset.WeakSet object at 0x00DCF990>,
'_abc_negative_cache_version': 12,
'_abc_registry': &lt;_weakrefset.WeakSet object at 0x00DCF910>,
'clear': &lt;function clear at 0x00DCB7C8>,
'copy': &lt;function copy at 0x00DD28A0>,
'fromkeys': &lt;classmethod object at 0x00DCF8F0>,
'items': &lt;function items at 0x00D60150>,
'keys': &lt;function keys at 0x00D60108>,
'pop': &lt;function pop at 0x00D60978>,
'popitem': &lt;function popitem at 0x00DCB780>,
'setdefault': &lt;function setdefault at 0x00D60A98>,
'update': &lt;function update at 0x00D60A50>,
'values': &lt;function values at 0x00D60198>}</samp></pre>
<ol>
<li>FIXME
</ol>
<p class=a>&#x2042;
<h2 id=implementing-fractions>Implementing Fractions</h2>
<p class=v><a rel=prev class=todo><span class=u>&#x261C;</span></a> <a rel=next class=todo><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
<script src=j/prettify.js></script>
<script src=j/dip3.js></script>
+3 -3
View File
@@ -205,7 +205,7 @@ AssertionError: Only for very large values of 2</samp></pre>
<a><samp class=p>>>> </samp><kbd class=pp>tuple(ord(c) for c in unique_characters)</kbd> <span class=u>&#x2463;</span></a>
<samp class=pp>(69, 68, 77, 79, 78, 83, 82, 89)</samp></pre>
<ol>
<li>A generator expression is like an anonymous function that yields values. The expression itself looks like a list comprehension [FIXME xref], but it&#8217;s wrapped in parentheses instead of square brackets.
<li>A generator expression is like an anonymous function that yields values. The expression itself looks like a <a href=comprehensions.htmllist-comprehensions>list comprehension</a>, but it&#8217;s wrapped in parentheses instead of square brackets.
<li>The generator expression returns&hellip; an iterator.
<li>Calling <code>next(<var>gen</var>)</code> returns the next value from the iterator.
<li>If you like, you can iterate through all the possible values and return a tuple, list, or set, by passing the generator expression to <code>tuple()</code>, <code>list()</code>, or <code>set()</code>. In these cases, you don&#8217;t need an extra set of parentheses&nbsp;&mdash;&nbsp;just pass the &#8220;bare&#8221; expression <code>ord(c) for c in unique_characters</code> to the <code>tuple()</code> function, and Python figures out that it&#8217;s a generator expression.
@@ -408,7 +408,7 @@ Wesley</samp></pre>
'N': '5', 'S': '1', 'R': '6', 'Y': '7'}</samp></pre>
<ol>
<li>Given a list of letters and a list of digits (each represented here as 1-character strings), the <code>zip</code> function will create a pairing of letters and digits, in order.
<li>Why is that cool? Because that data structure happens to be exactly the right structure to pass to the <code>dict()</code> function to create a dictionary that uses letters as keys and their associated digits as values. (This isn&#8217;t the only way to do it, of course. You could use a dictionary comprehension [FIXME xref] to create the dictionary directly.) Although the printed representation of the dictionary lists the pairs in a different order (dictionaries have no &#8220;order&#8221; per se), you can see that each letter is associated with the digit, based on the ordering of the original <var>characters</var> and <var>guess</var> sequences.
<li>Why is that cool? Because that data structure happens to be exactly the right structure to pass to the <code>dict()</code> function to create a dictionary that uses letters as keys and their associated digits as values. (This isn&#8217;t the only way to do it, of course. You could use a <a href=comprehensions.html#dictionary-comprehensions>dictionary comprehension</a> to create the dictionary directly.) Although the printed representation of the dictionary lists the pairs in a different order (dictionaries have no &#8220;order&#8221; per se), you can see that each letter is associated with the digit, based on the ordering of the original <var>characters</var> and <var>guess</var> sequences.
</ol>
<p id=guess>The alphametics solver uses this technique to create a dictionary that maps letters in the puzzle to digits in the solution, for each possible solution.
@@ -635,7 +635,7 @@ NameError: name '__import__' is not defined</samp></pre>
<p>Many, many thanks to Raymond Hettinger for agreeing to relicense his code so I could port it to Python 3 and use it as the basis for this chapter.
<p class=v><a href=iterators.html rel=prev title='back to &#8220;Iterators&#8221;'><span class=u>&#x261C;</span></a> <a href=unit-testing.html rel=next title='onward to &#8220;Unit Testing&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a href=iterators.html rel=prev title='back to &#8220;Classes &amp; Iterators&#8221;'><span class=u>&#x261C;</span></a> <a href=unit-testing.html rel=next title='onward to &#8220;Unit Testing&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a rel=prev class=todo><span class=u>&#x261C;</span></a> <a rel=next class=todo><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
+1 -1
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 18}
body{counter-reset:h1 17}
ins,del{line-height:2.154;text-decoration:none;font-style:normal;display:inline-block;width:100%}
ins{background:#9f9}
del{background:#f87}
+2 -1596
View File
File diff suppressed because it is too large Load Diff
+4 -6
View File
@@ -2,6 +2,10 @@
* Your First Python Program
** TODO mention why from module import * is only allowed at module level
* Native Datatypes
** TODO section (chapter?) on comprehensions
*** TODO list comprehensions
*** TODO set comprehensions
*** TODO dictionary comprehensions
* Strings
* Regular Expressions
* Closures & Generators
@@ -44,16 +48,10 @@
* TODO 2nd draft Special Method Names
* Bits to add somewhere
** TODO section on tuples
** TODO section (chapter?) on comprehensions
*** TODO list comprehensions
*** TODO set comprehensions
*** TODO dictionary comprehensions
** TODO section on dictionary views
several dictionary methods return them
they're dynamic
they update when the dictionary changes
** TODO function annotations?
** TODO PEP 8 style conventions
** TODO Decorators
[[http://docs.python.org/3.1/whatsnew/3.1.html][@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")]]
* Meta
+12 -2
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 12}
body{counter-reset:h1 11}
</style>
<link rel=stylesheet type=text/css media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
@@ -259,6 +259,8 @@ ValueError: I/O operation on closed file.</samp>
9 Alex
10 Lizzie</samp></pre>
<p class=a>&#x2042;
<h2 id=writing>Writing to Text Files</h2>
<p>You can write to files in much the same way that you read from them. First you open a file and get a file object, then you use methods on the file object to write data to the file, then you close the file.
@@ -296,6 +298,8 @@ ValueError: I/O operation on closed file.</samp>
<p>Did you notice the <code>encoding</code> parameter that got passed in to the <code>open()</code> function while you were <a href=#writing>opening a file for writing</a>? It&#8217;s important; don&#8217;t ever leave it out! As you saw in the beginning of this chapter, files don&#8217;t contain <i>strings</i>, they contain <i>bytes</i>. Reading a &#8220;string&#8221; from a text file only works because you told Python what encoding to use to read a stream of bytes and convert it to a string. Writing text to a file presents the same problem in reverse. You can&#8217;t write characters to a file; <a href=strings.html#byte-arrays>characters are an abstraction</a>. In order to write to the file, Python needs to know how to convert your string into a sequence of bytes. The only way to be sure it&#8217;s performing the correct conversion is to specify the <code>encoding</code> parameter when you open the file for writing.
<p class=a>&#x2042;
<h2 id=binary>Binary Files</h2>
<p class=ss><img src=examples/beauregard.jpg alt='my dog Beauregard' width=100 height=100>
@@ -343,6 +347,8 @@ AttributeError: '_io.BufferedReader' object has no attribute 'encoding'</samp></
<li>That means that there&#8217;s never <a href=#read>an unexpected mismatch</a> between the number you passed into the <code>read()</code> method and the position index you get out of the <code>tell()</code> method. The <code>read()</code> method reads bytes, and the <code>seek()</code> and <code>tell()</code> methods track the number of bytes read. For binary files, they&#8217;ll always agree.
</ol>
<p class=a>&#x2042;
<h2 id=file-like-objects>File-like Objects</h2>
<p>One of Python&#8217;s greatest strengths is its dynamic binding, and one powerful use of dynamic binding is the <dfn>file-like object</dfn>.
@@ -403,6 +409,8 @@ AttributeError: '_io.BufferedReader' object has no attribute 'encoding'</samp></
<samp class=p>you@localhost:~$ </samp><kbd>cat out.log</kbd>
<samp>A nine mile walk is no joke, especially in the rain.</samp></pre>
<p class=a>&#x2042;
<h2 id=stdio>Standard Input, Output, and Error</h2>
<p>Command-line gurus are already familiar with the concept of standard input, standard output, and standard error. This section is for the rest of you.
@@ -503,6 +511,8 @@ C</samp>
<p>Redirecting standard error works exactly the same way, using <code>sys.stderr</code> instead of <code>sys.stdout</code>.
<p class=a>&#x2042;
<h2 id=furtherreading>Further Reading</h2>
<ul>
@@ -512,7 +522,7 @@ C</samp>
<li><a href=http://en.wikipedia.org/wiki/Filesystem_in_Userspace><abbr>FUSE</abbr> on Wikipedia</a>
</ul>
<p class=v><a href=advanced-classes.html rel=prev title='back to &#8220;Advanced Classes&#8221;'><span class=u>&#x261C;</span></a> <a href=xml.html rel=next title='onward to &#8220;XML&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a href=refactoring.html rel=prev title='back to &#8220;Refactoring&#8221;'><span class=u>&#x261C;</span></a> <a href=xml.html rel=next title='onward to &#8220;XML&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
+2 -2
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 5}
body{counter-reset:h1 6}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
@@ -411,7 +411,7 @@ def plural(noun, rules_filename='plural5-rules.txt'):
<li><a href=http://www2.gsu.edu/~wwwesl/egw/crump.htm>English Irregular Plural Nouns</a>
</ul>
<p class=v><a href=regular-expressions.html rel=prev title='back to &#8220;Regular Expressions&#8221;'><span class=u>&#x261C;</span></a> <a href=iterators.html rel=next title='onward to &#8220;Iterators&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a href=regular-expressions.html rel=prev title='back to &#8220;Regular Expressions&#8221;'><span class=u>&#x261C;</span></a> <a href=iterators.html rel=next title='onward to &#8220;Classes &amp; Iterators&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
+1 -1
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 15}
body{counter-reset:h1 14}
mark{display:inline}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
+1 -2
View File
@@ -29,15 +29,14 @@ h1:before{content:''}
<li><a href=installing-python.html>Installing Python</a>
<li><a href=your-first-python-program.html>Your First Python Program</a>
<li><a href=native-datatypes.html>Native Datatypes</a>
<li><a href=comprehensions.html>Comprehensions</a>
<li><a href=strings.html>Strings</a>
<li><a href=regular-expressions.html>Regular Expressions</a>
<li><a href=generators.html>Closures <i class=baa>&amp;</i> Generators</a>
<li><a href=iterators.html>Classes <i class=baa>&amp;</i> Iterators</a>
<li><a href=advanced-iterators.html>Advanced Iterators</a>
<li><a href=unit-testing.html>Unit Testing</a>
<li class=todo>Advanced Unit Testing
<li><a href=refactoring.html>Refactoring</a>
<li><a href=advanced-classes.html>Advanced Classes</a>
<li><a href=files.html>Files</a>
<li><a href=xml.html>XML</a>
<li class=todo>Serializing Python Objects
-33
View File
@@ -1,33 +0,0 @@
<!DOCTYPE html>
<head>
<meta charset=utf-8>
<meta name=robots content=noindex>
<title>Secret Leftover Page - Dive into Python 3</title>
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 -1}
h1:before{counter-increment:h1;content:''}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
<meta name=viewport content='initial-scale=1.0'>
</head>
<form action=http://www.google.com/cse><div><input type=hidden name=cx value=014021643941856155761:l5eihuescdw><input type=hidden name=ie value=UTF-8>&nbsp;<input name=q size=25>&nbsp;<input type=submit name=sa value=Search></div></form>
<p>You are here: <a href=index.html>Home</a> <span class=u>&#8227;</span> <a href=table-of-contents.html>Dive Into Python 3</a> <span class=u>&#8227;</span>
<h1>Secret Leftover Page</h1>
<blockquote class=q>
<p><span class=u>&#x275D;</span> You step in the stream / but the water has moved on. / This page is not here. <span class=u>&#x275E;</span><br>&mdash; 404 Not Found haiku
</blockquote>
<p id=toc>&nbsp;
<h2 id=divingin>Huh?</h2>
<p class=f>This book used to have a chapter called &#8220;Iterators <i class=baa>&amp;</i> Generators,&#8221; but I split the chapter in half so I could introduce Python classes before talking about iterators. The content that used to be at this address is now in one of those two chapters:
<ul>
<li><a href=generators.html>Generators</a>
<li><a href=iterators.html>Iterators</a>
</ul>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
<script src=j/dip3.js></script>
<!--[if IE]><script src=j/html5.js></script><![endif]-->
+2 -2
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 6}
body{counter-reset:h1 7}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
@@ -383,7 +383,7 @@ rules = LazyRules()</code></pre>
<li><a href=http://www.dabeaz.com/generators/>Generator Tricks for Systems Programmers</a>
</ul>
<p class=v><a href=generators.html rel=prev title='back to &#8220;Generators&#8221;'><span class=u>&#x261C;</span></a> <a href=advanced-iterators.html rel=next title='onward to &#8220;Advanced Iterators&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a href=generators.html rel=prev title='back to &#8220;Closures &amp; Generators&#8221;'><span class=u>&#x261C;</span></a> <a href=advanced-iterators.html rel=next title='onward to &#8220;Advanced Iterators&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
+1 -1
View File
@@ -827,7 +827,7 @@ KeyError: 'db.diveintopython3.org'</samp></pre>
<li><a href=http://www.python.org/dev/peps/pep-0237/><abbr>PEP</abbr> 237: Unifying Long Integers and Integers</a>
<li><a href=http://www.python.org/dev/peps/pep-0238/><abbr>PEP</abbr> 238: Changing the Division Operator</a>
</ul>
<p class=v><a href=your-first-python-program.html rel=prev title='back to &#8220;Your First Python Program&#8221;'><span class=u>&#x261C;</span></a> <a href=strings.html rel=next title='onward to &#8220;Strings&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a href=your-first-python-program.html rel=prev title='back to &#8220;Your First Python Program&#8221;'><span class=u>&#x261C;</span></a> <a href=comprehensions.html rel=next title='onward to &#8220;Comprehensions&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
<script src=j/prettify.js></script>
+3 -3
View File
@@ -26,7 +26,7 @@ body{counter-reset:h1 10}
<a><samp class=p>>>> </samp><kbd class=pp>roman7.from_roman('')</kbd> <span class=u>&#x2460;</span></a>
<samp class=pp>0</samp></pre>
<ol>
<li>Remember in the [FIXME-xref] previous section when you kept seeing that an empty string would match the regular expression you were using to check for valid Roman numerals? Well, it turns out that this is still true for the final version of the regular expression. And that&#8217;s a bug; you want an empty string to raise an <code>InvalidRomanNumeralError</code> exception just like any other sequence of characters that don&#8217;t represent a valid Roman numeral.
<li>This is a bug. An empty string should raise an <code>InvalidRomanNumeralError</code> exception, just like any other sequence of characters that don&#8217;t represent a valid Roman numeral.
</ol>
<p>After reproducing the bug, and before fixing it, you should write a test case that fails, thus illustrating the bug.
@@ -120,7 +120,7 @@ Ran 11 tests in 0.156s
<h2 id=changing-requirements>Handling Changing Requirements</h2>
<p>Despite your best efforts to pin your customers to the ground and extract exact requirements from them on pain of horrible nasty things involving scissors and hot wax, requirements will change. Most customers don&#8217;t know what they want until they see it, and even if they do, they aren&#8217;t that good at articulating what they want precisely enough to be useful. And even if they do, they&#8217;ll want more in the next release anyway. So be prepared to update your test cases as requirements change.
<p>Suppose, for instance, that you wanted to expand the range of the Roman numeral conversion functions. Remember [FIXME-xref] the rule that said that no character could be repeated more than three times? Well, the Romans were willing to make an exception to that rule by having 4 <code>M</code> characters in a row to represent <code>4000</code>. If you make this change, you&#8217;ll be able to expand the range of convertible numbers from <code>1..3999</code> to <code>1..4999</code>. But first, you need to make some changes to your test cases.
<p>Suppose, for instance, that you wanted to expand the range of the Roman numeral conversion functions. Normally, no character in a Roman numeral can be repeated more than three times in a row. But the Romans were willing to make an exception to that rule by having 4 <code>M</code> characters in a row to represent <code>4000</code>. If you make this change, you&#8217;ll be able to expand the range of convertible numbers from <code>1..3999</code> to <code>1..4999</code>. But first, you need to make some changes to your test cases.
<p class=d>[<a href=examples/roman8.py>download <code>roman8.py</code></a>]
<pre><code class=pp>class KnownValues(unittest.TestCase):
@@ -471,7 +471,7 @@ OK</samp></pre>
<li>Refactoring mercilessly to improve performance, scalability, readability, maintainability, or whatever other -ility you&#8217;re lacking
</ul>
<p class=v><a rel=prev class=todo><span class=u>&#x261C;</span></a> <a rel=next class=todo><span class=u>&#x261E;</span></a>
<p class=v><a rel=prev class=todo><span class=u>&#x261C;</span></a> <a href=files.html rel=next title='onward to &#8220;Files&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
<script src=j/prettify.js></script>
+9 -9
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 4}
body{counter-reset:h1 5}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
@@ -259,13 +259,13 @@ body{counter-reset:h1 4}
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>pattern = '''
^ # beginning of string
M{0,3} # thousands - 0 to 3 M's
(CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
# or 500-800 (D, followed by 0 to 3 C's)
(XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
# or 50-80 (L, followed by 0 to 3 X's)
(IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
# or 5-8 (V, followed by 0 to 3 I's)
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
'''</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>re.search(pattern, 'M', re.VERBOSE)</kbd> <span class=u>&#x2460;</span></a>
@@ -437,7 +437,7 @@ AttributeError: 'NoneType' object has no attribute 'groups'</samp></pre>
<li><code>(x)</code> in general is a <em>remembered group</em>. You can get the value of what matched by using the <code>groups()</code> method of the object returned by <code>re.search</code>.
</ul>
<p>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.
<p class=v><a href=strings.html rel=prev title='back to &#8220;Strings&#8221;'><span class=u>&#x261C;</span></a> <a href=generators.html rel=next title='onward to &#8220;Generators&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a href=strings.html rel=prev title='back to &#8220;Strings&#8221;'><span class=u>&#x261C;</span></a> <a href=generators.html rel=next title='onward to &#8220;Closures &amp; Generators&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
<script src=j/prettify.js></script>
+3 -5
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 3}
body{counter-reset:h1 4}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
@@ -264,12 +264,10 @@ experience of years.</samp>
<ol>
<li>The <code><dfn>split</dfn>()</code> string method takes one argument, a delimiter, and split a string into a list of strings based on the delimiter. Here, the delimiter is an ampersand character, but it could be anything.
<li>Now we have a list of strings, each with a key, followed by an equals sign, followed by a value. We want to iterate over the entire list and split each string into two strings based on the first equals sign. (In theory, a value could contain an equals sign too. If we just used <code>'key=value=foo'.split('=')</code>, we would end up with a three-item list <code>['key', 'value', 'foo']</code>.)
<li>Now we have a list of strings, each with a key, followed by an equals sign, followed by a value. We can use a <a href=comprehensions.html#list-comprehensions>list comprehension</a> to iterate over the entire list and split each string into two strings based on the first equals sign. (In theory, a value could contain an equals sign too. If we just used <code>'key=value=foo'.split('=')</code>, we would end up with a three-item list <code>['key', 'value', 'foo']</code>.)
<li>Finally, Python can turn that list-of-lists into a dictionary simply by passing it to the <code>dict()</code> function.
</ol>
<p>[FIXME - this is the first time we've seen a list comprehension. Add a forward or backward reference once we have a full section explaining them.]
<blockquote class=note>
<p><span class=u>&#x261E;</span>The previous example looks a lot like parsing query parameters in a <abbr>URL</abbr>, but real-life <abbr>URL</abbr> parsing is actually more complicated than this. If you&#8217;re dealing with <abbr>URL</abbr> query parameters, you&#8217;re better off using the <a href=http://docs.python.org/3.1/library/urllib.parse.html#urllib.parse.parse_qs><code>urllib.parse.parse_qs()</code></a> function, which handles some non-obvious edge cases.
</blockquote>
@@ -459,7 +457,7 @@ TypeError: Can't convert 'bytes' object to str implicitly</samp>
<li><a href=http://www.python.org/dev/peps/pep-3101/><abbr>PEP</abbr> 3101: Advanced String Formatting</a>
</ul>
<p class=v><a href=native-datatypes.html rel=prev title='back to &#8220;Native Datatypes&#8221;'><span class=u>&#x261C;</span></a> <a href=regular-expressions.html rel=next title='onward to &#8220;Regular Expressions&#8221;'><span class=u>&#x261E;</span></a>
<p class=v><a href=comprehensions.html rel=prev title='back to &#8220;Comprehensions&#8221;'><span class=u>&#x261C;</span></a> <a href=regular-expressions.html rel=next title='onward to &#8220;Regular Expressions&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
+14 -16
View File
@@ -89,6 +89,10 @@ ul li ol{margin:0;padding:0 0 0 2.5em}
</ol>
<li><a href=native-datatypes.html#furtherreading>Further reading</a>
</ol>
<li id=comprehensions><a href=comprehensions.html>Comprehensions</a>
<ol>
<li>Diving In
</ol>
<li id=strings><a href=strings.html>Strings</a>
<ol>
<li><a href=strings.html#boring-stuff>Some Boring Stuff You Need To Understand Before You Can Dive In</a>
@@ -161,29 +165,23 @@ ul li ol{margin:0;padding:0 0 0 2.5em}
<li><a href=advanced-iterators.html#alphametics-finale>Putting It All Together</a>
<li><a href=advanced-iterators.html#furtherreading>Further Reading</a>
</ol>
<li id=unit-testing><a href=unit-testing.html>Unit testing</a>
<li id=unit-testing><a href=unit-testing.html>Unit Testing</a>
<ol>
<li><a href=unit-testing.html#divingin>(Not) diving in</a>
<li><a href=unit-testing.html#romantest1>A single question</a>
<li><a href=unit-testing.html#romantest2>&#8220;Halt and catch fire&#8221;</a>
<li><a href=unit-testing.html#romantest3>More halting, more fire</a>
<li>...
<li><a href=unit-testing.html#divingin>(Not) Diving In</a>
<li><a href=unit-testing.html#romantest1>A single Question</a>
<li><a href=unit-testing.html#romantest2>&#8220;Halt and Catch Fire&#8221;</a>
<li><a href=unit-testing.html#romantest3>More Halting, More Fire</a>
<li><a href=unit-testing.html#romantest4>And One More Thing&hellip;</a>
<li><a href=unit-testing.html#romantest5>A Pleasing Symmetry</a>
<li><a href=unit-testing.html#romantest6>More Bad Input</a>
</ol>
<li>Advanced Unit Testing
<ol>
<li>...
</ol>
<li><a href=refactoring.html>Refactoring your code</a>
<li><a href=refactoring.html>Refactoring</a>
<ol>
<li><a href=refactoring.html#divingin>Diving in</a>
<li><a href=refactoring.html#changing-requirements>Handling changing requirements</a>
<li><a href=refactoring.html#changing-requirements>Handling Changing Requirements</a>
<li><a href=refactoring.html#refactoring>Refactoring</a>
<li><a href=refactoring.html#summary>Summary</a>
</ol>
<li id=advanced-classes><a href=advanced-classes.html>Advanced Classes</a>
<ol>
<li><a href=advanced-classes.html#divingin>Diving in</a>
</ol>
<li id=files><a href=files.html>Files</a>
<ol>
<li><a href=files.html# id=divingin>Diving In</a>
+127 -7
View File
@@ -537,7 +537,7 @@ OK</samp></pre>
<p>But first, the tests. We&#8217;ll need a &#8220;known values&#8221; test to spot-check for accuracy. Our test suite already contains <a href=#romantest1>a mapping of known values</a>; let&#8217;s reuse that.
<pre class=nd><code> def test_from_roman_known_values(self):
<pre class=nd><code class=pp> def test_from_roman_known_values(self):
'''from_roman should give known result with known input'''
for integer, numeral in self.known_values:
result = roman5.from_roman(numeral)
@@ -545,11 +545,11 @@ OK</samp></pre>
<p>There&#8217;s a pleasing symmetry here. The <code>to_roman()</code> and <code>from_roman()</code> functions are inverses of each other. The first converts integers to specially-formatted strings, the second converts specially-formated strings to integers. In theory, we should be able to &#8220;round-trip&#8221; a number by passing to the <code>to_roman()</code> function to get a string, then passing that string to the <code>from_roman()</code> function to get an integer, and end up with the same number. In mathematical terms,
<pre class=nd><code>x = f(g(x)) for all values of x</code></pre>
<pre class=nd><code class=pp>x = f(g(x)) for all values of x</code></pre>
<p>In this case, &#8220;all values&#8221; means any number between <code>1..3999</code>, since that is the valid range of inputs to the <code>to_roman()</code> function. We can express this symmetry in a test case that runs through all the values <code>1..3999</code>, calls <code>to_roman()</code>, calls <code>from_roman()</code>, and checks that the output is the same as the original input.
<pre class=nd><code>class RoundtripCheck(unittest.TestCase):
<pre class=nd><code class=pp>class RoundtripCheck(unittest.TestCase):
def test_roundtrip(self):
'''from_roman(to_roman(n))==n for all n'''
for integer in range(1, 4000):
@@ -587,7 +587,7 @@ FAILED (errors=2)</samp></pre>
<p>A quick stub function will solve that problem.
<pre class=nd><code># roman5.py
<pre class=nd><code class=pp># roman5.py
def from_roman(s):
'''convert Roman numeral to integer'''</code></pre>
@@ -621,7 +621,7 @@ FAILED (failures=2)</samp></pre>
<p>Now it&#8217;s time to write the <code>from_roman()</code> function.
<pre><code>def from_roman(s):
<pre><code class=pp>def from_roman(s):
"""convert Roman numeral to integer"""
result = 0
index = 0
@@ -636,7 +636,7 @@ FAILED (failures=2)</samp></pre>
<p>If you're not clear how <code>from_roman()</code> works, add a <code>print</code> statement to the end of the <code>while</code> loop:
<pre><code>def from_roman(s):
<pre><code class=pp>def from_roman(s):
"""convert Roman numeral to integer"""
result = 0
index = 0
@@ -646,7 +646,7 @@ FAILED (failures=2)</samp></pre>
index += len(numeral)
<mark> print('found', numeral, 'of length', len(numeral), ', adding', integer)</mark></code></pre>
<pre class=screen>
<pre class='nd screen'>
<samp class=p>>>> </samp><kbd class=pp>import roman5</kbd>
<samp class=p>>>> </samp><kbd class=pp>roman5.from_roman('MCMLXXII')</kbd>
<samp class=pp>found M , of length 1, adding 1000
@@ -670,6 +670,126 @@ OK</samp></pre>
<p>Two pieces of exciting news here. The first is that the <code>from_roman()</code> function works for good input, at least for all the <a href=#romantest1>known values</a>. The second is that the &#8220;round trip&#8221; test also passed. Combined with the known values tests, you can be reasonably sure that both the <code>to_roman()</code> and <code>from_roman()</code> functions work properly for all possible good values. (This is not guaranteed; it is theoretically possible that <code>to_roman()</code> has a bug that produces the wrong Roman numeral for some particular set of inputs, <em>and</em> that <code>from_roman()</code> has a reciprocal bug that produces the same wrong integer values for exactly that set of Roman numerals that <code>to_roman()</code> generated incorrectly. Depending on your application and your requirements, this possibility may bother you; if so, write more comprehensive test cases until it doesn't bother you.)
<p class=a>&#x2042;
<h2 id=romantest6>More Bad Input</h2>
<p>Now that the <code>from_roman()</code> function works properly with good input, it's time to fit in the last piece of the puzzle: making it work properly with bad input. That means finding a way to look at a string and determine if it's a valid Roman numeral. This is inherently more difficult than <a href=#romantest3>validating numeric input</a> in the <code>to_roman()</code> function, but you have a powerful tool at your disposal: regular expressions. (If you&#8217;re not familiar with regular expressions, now would be a good time to read <a href=regular-expressions.html>the regular expressions chapter</a>.)
<p>As you saw in <a href=regular-expressions.html#romannumerals>Case Study: Roman Numerals</a>, there are several simple rules for constructing a Roman numeral, using the letters <code>M</code>, <code>D</code>, <code>C</code>, <code>L</code>, <code>X</code>, <code>V</code>, and <code>I</code>. Let's review the rules:
<ol>
<li>Characters are additive. <code>I</code> is <code>1</code>, <code>II</code> is <code>2</code>, and <code>III</code> is <code>3</code>. <code>VI</code> is <code>6</code> (literally, &#8220;<code>5</code> and <code>1</code>&#8221;), <code>VII</code> is <code>7</code>, and <code>VIII</code> is <code>8</code>.
<li>The tens characters (<code>I</code>, <code>X</code>, <code>C</code>, and <code>M</code>) can be repeated up to three times. At <code>4</code>, you need to subtract from the next highest fives character. You can't represent <code>4</code> as <code>IIII</code>; instead, it is represented as <code>IV</code> (&#8220;<code>1</code> less than <code>5</code>&#8221;). <code>40</code> is written as <code>XL</code> (&#8220;<code>10</code> less than <code>50</code>&#8221;), <code>41</code> as <code>XLI</code>, <code>42</code> as <code>XLII</code>, <code>43</code> as <code>XLIII</code>, and then <code>44</code> as <code>XLIV</code> (&#8220;<code>10</code> less than <code>50</code>, then <code>1</code> less than <code>5</code>&#8221;).
<li>Similarly, at <code>9</code>, you need to subtract from the next highest tens character: <code>8</code> is <code>VIII</code>, but <code>9</code> is <code>IX</code> (&#8220;<code>1</code> less than <code>10</code>&#8221;), not <code>VIIII</code> (since the <code>I</code> character can not be repeated four times). <code>90</code> is <code>XC</code>, <code>900</code> is <code>CM</code>.
<li>The fives characters can not be repeated. <code>10</code> is always represented as <code>X</code>, never as <code>VV</code>. <code>100</code> is always <code>C</code>, never <code>LL</code>.
<li>Roman numerals are always written highest to lowest, and read left to right, so order of characters matters very much. <code>DC</code> is <code>600</code>; <code>CD</code> is a completely different number (<code>400</code>, &#8220;<code>100</code> less than <code>500</code>&#8221;). <code>CI</code> is <code>101</code>; <code>IC</code> is not even a valid Roman numeral (because you can't subtract <code>1</code> directly from <code>100</code>; you would need to write it as <code>XCIX</code>, &#8220;<code>10</code> less than <code>100</code>, then <code>1</code> less than <code>10</code>&#8221;).
</ol>
<p>Thus, one useful test would be to ensure that the <code>from_roman()</code> function should fail when you pass it a string with too many repeated numerals. How many is &#8220;too many&#8221; depends on the numeral.
<pre class=nd><code class=pp>class FromRomanBadInput(unittest.TestCase):
def test_too_many_repeated_numerals(self):
'''from_roman should fail with too many repeated numerals'''
for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s)</code></pre>
<p>Another useful test would be to check that certain patterns aren&#8217;t repeated. For example, <code>IX</code> is <code>9</code>, but <code>IXIX</code> is never valid.
<pre class=nd><code class=pp> def test_repeated_pairs(self):
'''from_roman should fail with repeated pairs of numerals'''
for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):
self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s)</code></pre>
<p>A third test could check that numerals appear in the correct order, from highest to lowest value. For example, <code>CL</code> is <code>150</code>, but <code>LC</code> is never valid, because the numeral for <code>50</code> can never come before the numeral for <code>100</code>.
<pre class=nd><code class=pp> def test_malformed_antecedents(self):
'''from_roman should fail with malformed antecedents'''
for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s)</code></pre>
<p>Each of these tests relies the <code>from_roman()</code> function raising a new exception, <code>InvalidRomanNumeralError</code>, which we haven&#8217;t defined yet.
<pre class=nd><code class=pp># roman6.py
class InvalidRomanNumeralError(ValueError): pass</code></pre>
<p>All three of these tests should fail, since the <code>from_roman()</code> function doesn&#8217;t currently have any validity checking. (If they don&#8217;t fail now, then what the heck are they testing?)
<pre class='nd screen'>
<samp class=p>you@localhost:~/diveintopython3/examples$ </samp><kbd>python3 romantest6.py</kbd>
<samp>FFF.......
======================================================================
FAIL: test_malformed_antecedents (__main__.FromRomanBadInput)
from_roman should fail with malformed antecedents
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest6.py", line 113, in test_malformed_antecedents
self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s)
AssertionError: InvalidRomanNumeralError not raised by from_roman
======================================================================
FAIL: test_repeated_pairs (__main__.FromRomanBadInput)
from_roman should fail with repeated pairs of numerals
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest6.py", line 107, in test_repeated_pairs
self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s)
AssertionError: InvalidRomanNumeralError not raised by from_roman
======================================================================
FAIL: test_too_many_repeated_numerals (__main__.FromRomanBadInput)
from_roman should fail with too many repeated numerals
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest6.py", line 102, in test_too_many_repeated_numerals
self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s)
AssertionError: InvalidRomanNumeralError not raised by from_roman
----------------------------------------------------------------------
Ran 10 tests in 0.058s
FAILED (failures=3)</samp></pre>
<p>Good deal. Now, all we need to do is add the <a href=regular-expressions.html#romannumerals>regular expression to test for valid Roman numerals</a> into the <code>from_roman()</code> function.
<pre class=nd><code class=pp>roman_numeral_pattern = re.compile('''
^ # 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.VERBOSE)
def from_roman(s):
'''convert Roman numeral to integer'''
<mark> if not roman_numeral_pattern.search(s):
raise InvalidRomanNumeralError('Invalid Roman numeral: {0}'.format(s))</mark>
result = 0
index = 0
for numeral, integer in roman_numeral_map:
while s[index : index + len(numeral)] == numeral:
result += integer
index += len(numeral)
return result</code></pre>
<p>And re-run the tests&hellip;
<pre class='nd screen'>
<samp class=p>you@localhost:~/diveintopython3/examples$ </samp><kbd>python3 romantest7.py</kbd>
<samp>..........
----------------------------------------------------------------------
Ran 10 tests in 0.066s
OK</samp></pre>
<p>And the anticlimax award of the year goes to&hellip; the word &#8220;<code>OK</code>&#8221;, which is printed by the <code>unittest</code> module when all the tests pass.
<p class=v><a href=advanced-iterators.html rel=prev title='back to &#8220;Advanced Iterators&#8221;'><span class=u>&#x261C;</span></a> <a href=unit-testing.html rel=next title='onward to &#8220;Unit Testing&#8221;'><span class=u>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=j/jquery.js></script>
+1 -1
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 19}
body{counter-reset:h1 18}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
+1 -1
View File
@@ -5,7 +5,7 @@
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 13}
body{counter-reset:h1 12}
mark{display:inline}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>