From c8080fdbd121647e84105b06a21c57cda5307082 Mon Sep 17 00:00:00 2001 From: Mark Pilgrim Date: Sat, 28 Feb 2009 16:21:16 -0500 Subject: [PATCH] another section of unit-testing --- case-study-porting-chardet-to-python-3.html | 7 +- dip2 | 913 -- dip3.css | 12 +- dip3.js | 1 + jquery.js | 8329 ++++++++++--------- native-datatypes.html | 7 +- porting-code-to-python-3-with-2to3.html | 7 +- push | 6 + regular-expressions.html | 7 +- roman2.py | 7 +- table-of-contents.html | 17 +- unit-testing.html | 122 +- your-first-python-program.html | 7 +- 13 files changed, 4383 insertions(+), 5059 deletions(-) create mode 100644 push diff --git a/case-study-porting-chardet-to-python-3.html b/case-study-porting-chardet-to-python-3.html index 2f68afb..715fdbb 100644 --- a/case-study-porting-chardet-to-python-3.html +++ b/case-study-porting-chardet-to-python-3.html @@ -12,7 +12,7 @@ body{counter-reset:h1 20}

skip to main content

-

Case study: porting chardet to Python 3

Words, words. They’re all we have to go on.
Rosencrantz and Guildenstern are Dead @@ -98,6 +98,7 @@ body{counter-reset:h1 20}

Running 2to3

We’re going to migrate the chardet module from Python 2 to Python 3. Python 3 comes with a utility script called 2to3, which takes your actual Python 2 source code as input and auto-converts as much as it can to Python 3. In some cases this is easy -- a function was renamed or moved to a different modules -- but in other cases it can get pretty complex. To get a sense of all that it can do, refer to the appendix, Porting code to Python 3 with 2to3. In this chapter, we’ll start by running 2to3 on the chardet package, but as you’ll see, there will still be a lot of work to do after the automated tools have performed their magic.

The main chardet package is split across several different files, all in the same directory. The 2to3 script makes it easy to convert multiple files at once: just pass a directory as a command line argument, and 2to3 will convert each of the files in turn. +

[The code examples will be easier to follow if you enable Javascript, but whatever.]

skip over this

C:\home\chardet> python c:\Python30\Tools\Scripts\2to3.py -w chardet\
 RefactoringTool: Skipping implicit fixer: buffer
@@ -717,5 +718,5 @@ for line in open(f, 'rb'):
 TypeError: Can't convert 'bytes' object to str implicitly

...

© 2001–4, 2009 ark Pilgrim, CC-BY-SA-3.0 - - + + diff --git a/dip2 b/dip2 index fe207f6..3fa4ea4 100644 --- a/dip2 +++ b/dip2 @@ -5801,660 +5801,6 @@ def fetch(source, etag=None, last_modified=None, agent=USER_AGENT):

  • Supporting gzip compression to reduce bandwidth even when data has changed. - -
    -

    Chapter 12. SOAP Web Services

    -

    Chapter 11 focused on document-oriented web services over HTTP. The “input parameter” was the URL, and the “return value” was an actual XML document which it was your responsibility to parse. -

    This chapter will focus on SOAP web services, which take a more structured approach. Rather than dealing with HTTP requests and XML documents directly, -SOAP allows you to simulate calling functions that return native data types. As you will see, the illusion is almost perfect; -you can “call” a function through a SOAP library, with the standard Python calling syntax, and the function appears to return Python objects and values. But under the covers, the SOAP library has actually performed a complex transaction involving multiple XML documents and a remote server. -

    SOAP is a complex specification, and it is somewhat misleading to say that SOAP is all about calling remote functions. Some people would pipe up to add that SOAP allows for one-way asynchronous message passing, and document-oriented web services. And those people would be correct; -SOAP can be used that way, and in many different ways. But this chapter will focus on so-called “RPC-style” SOAP -- calling a remote function and getting results back. -

    12.1. Diving In

    -

    You use Google, right? It's a popular search engine. Have you ever wished you could programmatically access Google search - results? Now you can. Here is a program to search Google from Python. -

    Example 12.1. search.py

    from SOAPpy import WSDL
    -
    -# you'll need to configure these two values;
    -# see http://www.google.com/apis/
    -WSDLFILE = '/path/to/copy/of/GoogleSearch.wsdl'
    -APIKEY = 'YOUR_GOOGLE_API_KEY'
    -
    -_server = WSDL.Proxy(WSDLFILE)
    -def search(q):
    -    """Search Google and return list of {title, link, description}"""
    -    results = _server.doGoogleSearch(
    -        APIKEY, q, 0, 10, False, "", False, "", "utf-8", "utf-8")
    -    return [{"title": r.title.encode("utf-8"),
    -             "link": r.URL.encode("utf-8"),
    -             "description": r.snippet.encode("utf-8")}
    -            for r in results.resultElements]
    -
    -if __name__ == '__main__':
    -    import sys
    -    for r in search(sys.argv[1])[:5]:
    -        print r['title']
    -        print r['link']
    -        print r['description']
    -        print

    You can import this as a module and use it from a larger program, or you can run the script from the command line. On the -command line, you give the search query as a command-line argument, and it prints out the URL, title, and description of the -top five Google search results. -

    Here is the sample output for a search for the word “python”. -

    Example 12.2. Sample Usage of search.py

    -C:\diveintopython3\common\py> python search.py "python"
    -<b>Python</b> Programming Language
    -http://www.python.org/
    -Home page for <b>Python</b>, an interpreted, interactive, object-oriented,
    -extensible<br> programming language. <b>...</b> <b>Python</b>
    -is OSI Certified Open Source: OSI Certified.
    -
    -<b>Python</b> Documentation Index
    -http://www.python.org/doc/
    - <b>...</b> New-style classes (aka descrintro). Regular expressions. Database
    -API. Email Us.<br> docs@<b>python</b>.org. (c) 2004. <b>Python</b>
    -Software Foundation. <b>Python</b> Documentation. <b>...</b>
    -
    -Download <b>Python</b> Software
    -http://www.python.org/download/
    -Download Standard <b>Python</b> Software. <b>Python</b> 2.3.3 is the
    -current production<br> version of <b>Python</b>. <b>...</b>
    -<b>Python</b> is OSI Certified Open Source:
    -
    -Pythonline
    -http://www.pythonline.com/
    -
    -
    -Dive Into <b>Python</b>
    -http://diveintopython3.org/
    -Dive Into <b>Python</b>. <b>Python</b> from novice to pro. Find:
    -<b>...</b> It is also available in multiple<br> languages. Read
    -Dive Into <b>Python</b>. This book is still being written. <b>...</b>
    -
    -

    Further Reading on SOAP

    - -

    12.2. Installing the SOAP Libraries

    -

    Unlike the other code in this book, this chapter relies on libraries that do not come pre-installed with Python. -

    Before you can dive into SOAP web services, you'll need to install three libraries: PyXML, fpconst, and SOAPpy. -

    12.2.1. Installing PyXML

    -

    The first library you need is PyXML, an advanced set of XML libraries that provide more functionality than the built-in XML libraries we studied in Chapter 9. -

    -

    Procedure 12.1.

    -

    Here is the procedure for installing PyXML: -

      -
    1. -

      Go to http://pyxml.sourceforge.net/, click Downloads, and download the latest version for your operating system. - -

    2. -

      If you are using Windows, there are several choices. Make sure to download the version of PyXML that matches the version of Python you are using. - -

    3. -

      Double-click the installer. If you download PyXML 0.8.3 for Windows and Python 2.3, the installer program will be PyXML-0.8.3.win32-py2.3.exe. - -

    4. -

      Step through the installer program. - -

    5. -

      After the installation is complete, close the installer. There will not be any visible indication of success (no programs - installed on the Start Menu or shortcuts installed on the desktop). PyXML is simply a collection of XML libraries used by other programs. - -

    -

    To verify that you installed PyXML correctly, run your Python IDE and check the version of the XML libraries you have installed, as shown here. -

    Example 12.3. Verifying PyXML Installation

    ->>> import xml
    ->>> xml.__version__
    -'0.8.3'
    -

    This version number should match the version number of the PyXML installer program you downloaded and ran. -

    12.2.2. Installing fpconst

    -

    The second library you need is fpconst, a set of constants and functions for working with IEEE754 double-precision special values. This provides support for the - special values Not-a-Number (NaN), Positive Infinity (Inf), and Negative Infinity (-Inf), which are part of the SOAP datatype specification. -

    -

    Procedure 12.2.

    -

    Here is the procedure for installing fpconst: -

      -
    1. -

      Download the latest version of fpconst from http://www.analytics.washington.edu/statcomp/projects/rzope/fpconst/. - -

    2. -

      There are two downloads available, one in .tar.gz format, the other in .zip format. If you are using Windows, download the .zip file; otherwise, download the .tar.gz file. - -

    3. -

      Decompress the downloaded file. On Windows XP, you can right-click on the file and choose Extract All; on earlier versions - of Windows, you will need a third-party program such as WinZip. On Mac OS X, you can double-click the compressed file to decompress it with Stuffit Expander. - -

    4. -

      Open a command prompt and navigate to the directory where you decompressed the fpconst files. - -

    5. -

      Type python setup.py install to run the installation program. - -

    -

    To verify that you installed fpconst correctly, run your Python IDE and check the version number. -

    Example 12.4. Verifying fpconst Installation

    ->>> import fpconst
    ->>> fpconst.__version__
    -'0.6.0'
    -

    This version number should match the version number of the fpconst archive you downloaded and installed. -

    12.2.3. Installing SOAPpy

    -

    The third and final requirement is the SOAP library itself: SOAPpy. -

    -

    Procedure 12.3.

    -

    Here is the procedure for installing SOAPpy: -

      -
    1. -

      Go to http://pywebsvcs.sourceforge.net/ and select Latest Official Release under the SOAPpy section. - -

    2. -

      There are two downloads available. If you are using Windows, download the .zip file; otherwise, download the .tar.gz file. - -

    3. -

      Decompress the downloaded file, just as you did with fpconst. - -

    4. -

      Open a command prompt and navigate to the directory where you decompressed the SOAPpy files. - -

    5. -

      Type python setup.py install to run the installation program. - -

    -

    To verify that you installed SOAPpy correctly, run your Python IDE and check the version number. -

    Example 12.5. Verifying SOAPpy Installation

    ->>> import SOAPpy
    ->>> SOAPpy.__version__
    -'0.11.4'
    -

    This version number should match the version number of the SOAPpy archive you downloaded and installed. -

    12.3. First Steps with SOAP

    -

    The heart of SOAP is the ability to call remote functions. There are a number of public access SOAP servers that provide simple functions for demonstration purposes. -

    The most popular public access SOAP server is http://www.xmethods.net/. This example uses a demonstration function that takes a United States zip code and returns the current temperature in that -region. -

    Example 12.6. Getting the Current Temperature

    ->>> from SOAPpy import SOAPProxy            
    ->>> url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'
    ->>> namespace = 'urn:xmethods-Temperature'  
    ->>> server = SOAPProxy(url, namespace)      
    ->>> server.getTemp('27502')                 
    -80.0
    -
    -
      -
    1. You access the remote SOAP server through a proxy class, SOAPProxy. The proxy handles all the internals of SOAP for you, including creating the XML request document out of the function name and argument list, sending the request over - HTTP to the remote SOAP server, parsing the XML response document, and creating native Python values to return. You'll see what these XML documents look like in the next section. -
    2. Every SOAP service has a URL which handles all the requests. The same URL is used for all function calls. This particular service only has a single function, but later in this chapter you'll see - examples of the Google API, which has several functions. The service URL is shared by all functions.Each SOAP service also has a namespace, which is defined by the server and is completely arbitrary. It's simply part of the configuration - required to call SOAP methods. It allows the server to share a single service URL and route requests between several unrelated services. It's like dividing Python modules into packages. -
    3. You're creating the SOAPProxy with the service URL and the service namespace. This doesn't make any connection to the SOAP server; it simply creates a local Python object. -
    4. Now with everything configured properly, you can actually call remote SOAP methods as if they were local functions. You pass arguments just like a normal function, and you get a return value just - like a normal function. But under the covers, there's a heck of a lot going on. -

      Let's peek under those covers. -

      12.4. Debugging SOAP Web Services

      -

      The SOAP libraries provide an easy way to see what's going on behind the scenes. -

      Turning on debugging is a simple matter of setting two flags in the SOAPProxy's configuration. -

      Example 12.7. Debugging SOAP Web Services

      ->>> from SOAPpy import SOAPProxy
      ->>> url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'
      ->>> n = 'urn:xmethods-Temperature'
      ->>> server = SOAPProxy(url, namespace=n)     
      ->>> server.config.dumpSOAPOut = 1            
      ->>> server.config.dumpSOAPIn = 1
      ->>> temperature = server.getTemp('27502')    
      -*** Outgoing SOAP ******************************************************
      -<?xml version="1.0" encoding="UTF-8"?>
      -<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
      -  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
      -  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
      -  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
      -  xmlns:xsd="http://www.w3.org/1999/XMLSchema">
      -<SOAP-ENV:Body>
      -<ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1">
      -<v1 xsi:type="xsd:string">27502</v1>
      -</ns1:getTemp>
      -</SOAP-ENV:Body>
      -</SOAP-ENV:Envelope>
      -************************************************************************
      -*** Incoming SOAP ******************************************************
      -<?xml version='1.0' encoding='UTF-8'?>
      -<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
      -  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      -  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      -<SOAP-ENV:Body>
      -<ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature"
      -  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      -<return xsi:type="xsd:float">80.0</return>
      -</ns1:getTempResponse>
      -
      -</SOAP-ENV:Body>
      -</SOAP-ENV:Envelope>
      -************************************************************************
      -
      ->>> temperature
      -80.0
      -
      -
        -
      1. First, create the SOAPProxy like normal, with the service URL and the namespace. -
      2. Second, turn on debugging by setting server.config.dumpSOAPIn and server.config.dumpSOAPOut. -
      3. Third, call the remote SOAP method as usual. The SOAP library will print out both the outgoing XML request document, and the incoming XML response document. This is all the hard - work that SOAPProxy is doing for you. Intimidating, isn't it? Let's break it down. -

        Most of the XML request document that gets sent to the server is just boilerplate. Ignore all the namespace declarations; -they're going to be the same (or similar) for all SOAP calls. The heart of the “function call” is this fragment within the <Body> element: -

        
        -<ns1:getTemp               
        -  xmlns:ns1="urn:xmethods-Temperature"       
        -  SOAP-ENC:root="1">
        -<v1 xsi:type="xsd:string">27502</v1>         
        -</ns1:getTemp>
        -
        -
          -
        1. The element name is the function name, getTemp. SOAPProxy uses getattr as a dispatcher. Instead of calling separate local methods based on the method name, it actually uses the method name to construct the XML - request document. -
        2. The function's XML element is contained in a specific namespace, which is the namespace you specified when you created the -SOAPProxy object. Don't worry about the SOAP-ENC:root; that's boilerplate too. -
        3. The arguments of the function also got translated into XML. SOAPProxy introspects each argument to determine its datatype (in this case it's a string). The argument datatype goes into the xsi:type attribute, followed by the actual string value. -

          The XML return document is equally easy to understand, once you know what to ignore. Focus on this fragment within the <Body>: -

          
          -<ns1:getTempResponse           
          -  xmlns:ns1="urn:xmethods-Temperature"           
          -  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
          -<return xsi:type="xsd:float">80.0</return>       
          -</ns1:getTempResponse>
          -
          -
            -
          1. The server wraps the function return value within a <getTempResponse> element. By convention, this wrapper element is the name of the function, plus Response. But it could really be almost anything; the important thing that SOAPProxy notices is not the element name, but the namespace. -
          2. The server returns the response in the same namespace we used in the request, the same namespace we specified when we first - create the SOAPProxy. Later in this chapter we'll see what happens if you forget to specify the namespace when creating the SOAPProxy. -
          3. The return value is specified, along with its datatype (it's a float). SOAPProxy uses this explicit datatype to create a Python object of the correct native datatype and return it. -

            12.5. Introducing WSDL

            -

            The SOAPProxy class proxies local method calls and transparently turns then into invocations of remote SOAP methods. As you've seen, this is a lot of work, and SOAPProxy does it quickly and transparently. What it doesn't do is provide any means of method introspection. -

            Consider this: the previous two sections showed an example of calling a simple remote SOAP method with one argument and one return value, both of simple data types. This required knowing, and keeping track of, the -service URL, the service namespace, the function name, the number of arguments, and the datatype of each argument. If any of these is -missing or wrong, the whole thing falls apart. -

            That shouldn't come as a big surprise. If I wanted to call a local function, I would need to know what package or module -it was in (the equivalent of service URL and namespace). I would need to know the correct function name and the correct number of arguments. Python deftly handles datatyping without explicit types, but I would still need to know how many argument to pass, and how many -return values to expect. -

            The big difference is introspection. As you saw in Chapter 4, Python excels at letting you discover things about modules and functions at runtime. You can list the available functions within -a module, and with a little work, drill down to individual function declarations and arguments. -

            WSDL lets you do that with SOAP web services. WSDL stands for “Web Services Description Language”. Although designed to be flexible enough to describe many types of web services, it is most often used to describe SOAP web services. -

            A WSDL file is just that: a file. More specifically, it's an XML file. It usually lives on the same server you use to access the -SOAP web services it describes, although there's nothing special about it. Later in this chapter, we'll download the WSDL file for the Google API and use it locally. That doesn't mean we're calling Google locally; the WSDL file still describes the remote functions sitting on Google's server. -

            A WSDL file contains a description of everything involved in calling a SOAP web service: -

            -
              -
            • The service URL and namespace - -
            • The type of web service (probably function calls using SOAP, although as I mentioned, WSDL is flexible enough to describe a wide variety of web services) - -
            • The list of available functions -
            • The arguments for each function -
            • The datatype of each argument -
            • The return values of each function, and the datatype of each return value -
            -

            In other words, a WSDL file tells you everything you need to know to be able to call a SOAP web service. -

            12.6. Introspecting SOAP Web Services with WSDL

            -

            Like many things in the web services arena, WSDL has a long and checkered history, full of political strife and intrigue. I will skip over this history entirely, since it - bores me to tears. There were other standards that tried to do similar things, but WSDL won, so let's learn how to use it. -

            The most fundamental thing that WSDL allows you to do is discover the available methods offered by a SOAP server. -

            Example 12.8. Discovering The Available Methods

            ->>> from SOAPpy import WSDL          
            ->>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl')
            ->>> server = WSDL.Proxy(wsdlFile)    
            ->>> server.methods.keys()            
            -[u'getTemp']
            -
            -
              -
            1. SOAPpy includes a WSDL parser. At the time of this writing, it was labeled as being in the early stages of development, but I had no problem parsing - any of the WSDL files I tried. -
            2. To use a WSDL file, you again use a proxy class, WSDL.Proxy, which takes a single argument: the WSDL file. Note that in this case you are passing in the URL of a WSDL file stored on the remote server, but the proxy class works just as well with a local copy of the WSDL file. The act of creating the WSDL proxy will download the WSDL file and parse it, so it there are any errors in the WSDL file (or it can't be fetched due to networking problems), you'll know about it immediately. -
            3. The WSDL proxy class exposes the available functions as a Python dictionary, server.methods. So getting the list of available methods is as simple as calling the dictionary method keys(). -

              Okay, so you know that this SOAP server offers a single method: getTemp. But how do you call it? The WSDL proxy object can tell you that too. -

              Example 12.9. Discovering A Method's Arguments

              ->>> callInfo = server.methods['getTemp']  
              ->>> callInfo.inparams   
              -[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AD0>]
              ->>> callInfo.inparams[0].name             
              -u'zipcode'
              ->>> callInfo.inparams[0].type             
              -(u'http://www.w3.org/2001/XMLSchema', u'string')
              -
              -
                -
              1. The server.methods dictionary is filled with a SOAPpy-specific structure called CallInfo. A CallInfo object contains information about one specific function, including the function arguments. -
              2. The function arguments are stored in callInfo.inparams, which is a Python list of ParameterInfo objects that hold information about each parameter. -
              3. Each ParameterInfo object contains a name attribute, which is the argument name. You are not required to know the argument name to call the function through SOAP, but SOAP does support calling functions with named arguments (just like Python), and WSDL.Proxy will correctly handle mapping named arguments to the remote function if you choose to use them. -
              4. Each parameter is also explicitly typed, using datatypes defined in XML Schema. You saw this in the wire trace in the previous - section; the XML Schema namespace was part of the “boilerplate” I told you to ignore. For our purposes here, you may continue to ignore it. The zipcode parameter is a string, and if you pass in a Python string to the WSDL.Proxy object, it will map it correctly and send it to the server. -

                WSDL also lets you introspect into a function's return values. -

                Example 12.10. Discovering A Method's Return Values

                ->>> callInfo.outparams            
                -[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AF8>]
                ->>> callInfo.outparams[0].name    
                -u'return'
                ->>> callInfo.outparams[0].type
                -(u'http://www.w3.org/2001/XMLSchema', u'float')
                -
                -
                  -
                1. The adjunct to callInfo.inparams for function arguments is callInfo.outparams for return value. It is also a list, because functions called through SOAP can return multiple values, just like Python functions. -
                2. Each ParameterInfo object contains name and type. This function returns a single value, named return, which is a float. -

                  Let's put it all together, and call a SOAP web service through a WSDL proxy. -

                  Example 12.11. Calling A Web Service Through A WSDL Proxy

                  ->>> from SOAPpy import WSDL
                  ->>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl')
                  ->>> server = WSDL.Proxy(wsdlFile)               
                  ->>> server.getTemp('90210')   
                  -66.0
                  ->>> server.soapproxy.config.dumpSOAPOut = 1     
                  ->>> server.soapproxy.config.dumpSOAPIn = 1
                  ->>> temperature = server.getTemp('90210')
                  -*** Outgoing SOAP ******************************************************
                  -<?xml version="1.0" encoding="UTF-8"?>
                  -<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                  -  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
                  -  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
                  -  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                  -  xmlns:xsd="http://www.w3.org/1999/XMLSchema">
                  -<SOAP-ENV:Body>
                  -<ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1">
                  -<v1 xsi:type="xsd:string">90210</v1>
                  -</ns1:getTemp>
                  -</SOAP-ENV:Body>
                  -</SOAP-ENV:Envelope>
                  -************************************************************************
                  -*** Incoming SOAP ******************************************************
                  -<?xml version='1.0' encoding='UTF-8'?>
                  -<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                  -  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  -  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
                  -<SOAP-ENV:Body>
                  -<ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature"
                  -  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
                  -<return xsi:type="xsd:float">66.0</return>
                  -</ns1:getTempResponse>
                  -
                  -</SOAP-ENV:Body>
                  -</SOAP-ENV:Envelope>
                  -************************************************************************
                  -
                  ->>> temperature
                  -66.0
                  -
                  -
                    -
                  1. The configuration is simpler than calling the SOAP service directly, since the WSDL file contains the both service URL and namespace you need to call the service. Creating the WSDL.Proxy object downloads the WSDL file, parses it, and configures a SOAPProxy object that it uses to call the actual SOAP web service. -
                  2. Once the WSDL.Proxy object is created, you can call a function as easily as you did with the SOAPProxy object. This is not surprising; the WSDL.Proxy is just a wrapper around the SOAPProxy with some introspection methods added, so the syntax for calling functions is the same. -
                  3. You can access the WSDL.Proxy's SOAPProxy with server.soapproxy. This is useful to turning on debugging, so that when you can call functions through the WSDL proxy, its SOAPProxy will dump the outgoing and incoming XML documents that are going over the wire. -

                    12.7. Searching Google

                    -

                    Let's finally turn to the sample code that you saw that the beginning of this chapter, which does something more useful and - exciting than get the current temperature. -

                    Google provides a SOAP API for programmatically accessing Google search results. To use it, you will need to sign up for Google Web Services. -

                    -

                    Procedure 12.4. Signing Up for Google Web Services

                    -
                      -
                    1. -

                      Go to http://www.google.com/apis/ and create a Google account. This requires only an email address. After you sign up you will receive your Google API license - key by email. You will need this key to pass as a parameter whenever you call Google's search functions. - -

                    2. -

                      Also on http://www.google.com/apis/, download the Google Web APIs developer kit. This includes some sample code in several programming languages (but not Python), and more importantly, it includes the WSDL file. - -

                    3. -

                      Decompress the developer kit file and find GoogleSearch.wsdl. Copy this file to some permanent location on your local drive. You will need it later in this chapter. - -

                    -

                    Once you have your developer key and your Google WSDL file in a known place, you can start poking around with Google Web Services. -

                    Example 12.12. Introspecting Google Web Services

                    ->>> from SOAPpy import WSDL
                    ->>> server = WSDL.Proxy('/path/to/your/GoogleSearch.wsdl') 
                    ->>> server.methods.keys()                
                    -[u'doGoogleSearch', u'doGetCachedPage', u'doSpellingSuggestion']
                    ->>> callInfo = server.methods['doGoogleSearch']
                    ->>> for arg in callInfo.inparams:        
                    -...    print arg.name.ljust(15), arg.type
                    -key             (u'http://www.w3.org/2001/XMLSchema', u'string')
                    -q               (u'http://www.w3.org/2001/XMLSchema', u'string')
                    -start           (u'http://www.w3.org/2001/XMLSchema', u'int')
                    -maxResults      (u'http://www.w3.org/2001/XMLSchema', u'int')
                    -filter          (u'http://www.w3.org/2001/XMLSchema', u'boolean')
                    -restrict        (u'http://www.w3.org/2001/XMLSchema', u'string')
                    -safeSearch      (u'http://www.w3.org/2001/XMLSchema', u'boolean')
                    -lr              (u'http://www.w3.org/2001/XMLSchema', u'string')
                    -ie              (u'http://www.w3.org/2001/XMLSchema', u'string')
                    -oe              (u'http://www.w3.org/2001/XMLSchema', u'string')
                    -
                    -
                      -
                    1. Getting started with Google web services is easy: just create a WSDL.Proxy object and point it at your local copy of Google's WSDL file. -
                    2. According to the WSDL file, Google offers three functions: doGoogleSearch, doGetCachedPage, and doSpellingSuggestion. These do exactly what they sound like: perform a Google search and return the results programmatically, get access to the - cached version of a page from the last time Google saw it, and offer spelling suggestions for commonly misspelled search words. -
                    3. The doGoogleSearch function takes a number of parameters of various types. Note that while the WSDL file can tell you what the arguments are called and what datatype they are, it can't tell you what they mean or how to use - them. It could theoretically tell you the acceptable range of values for each parameter, if only specific values were allowed, - but Google's WSDL file is not that detailed. WSDL.Proxy can't work magic; it can only give you the information provided in the WSDL file. -

                      Here is a brief synopsis of all the parameters to the doGoogleSearch function: -

                      -
                        -
                      • key - Your Google API key, which you received when you signed up for Google web services. - -
                      • q - The search word or phrase you're looking for. The syntax is exactly the same as Google's web form, so if you know any - advanced search syntax or tricks, they all work here as well. - -
                      • start - The index of the result to start on. Like the interactive web version of Google, this function returns 10 results at a - time. If you wanted to get the second “page” of results, you would set start to 10. - -
                      • maxResults - The number of results to return. Currently capped at 10, although you can specify fewer if you are only interested in - a few results and want to save a little bandwidth. - -
                      • filter - If True, Google will filter out duplicate pages from the results. - -
                      • restrict - Set this to country plus a country code to get results only from a particular country. Example: countryUK to search pages in the United Kingdom. You can also specify linux, mac, or bsd to search a Google-defined set of technical sites, or unclesam to search sites about the United States government. - -
                      • safeSearch - If True, Google will filter out porn sites. - -
                      • lr (“language restrict”) - Set this to a language code to get results only in a particular language. - -
                      • ie and oe (“input encoding” and “output encoding”) - Deprecated, both must be utf-8. - -
                      -

                      Example 12.13. Searching Google

                      ->>> from SOAPpy import WSDL
                      ->>> server = WSDL.Proxy('/path/to/your/GoogleSearch.wsdl')
                      ->>> key = 'YOUR_GOOGLE_API_KEY'
                      ->>> results = server.doGoogleSearch(key, 'mark', 0, 10, False, "",
                      -...    False, "", "utf-8", "utf-8")             
                      ->>> len(results.resultElements)
                      -10
                      ->>> results.resultElements[0].URL                
                      -'http://diveintomark.org/'
                      ->>> results.resultElements[0].title
                      -'dive into <b>mark</b>'
                      -
                      -
                        -
                      1. After setting up the WSDL.Proxy object, you can call server.doGoogleSearch with all ten parameters. Remember to use your own Google API key that you received when you signed up for Google web services. -
                      2. There's a lot of information returned, but let's look at the actual search results first. They're stored in results.resultElements, and you can access them just like a normal Python list. -
                      3. Each element in the resultElements is an object that has a URL, title, snippet, and other useful attributes. At this point you can use normal Python introspection techniques like dir(results.resultElements[0]) to see the available attributes. Or you can introspect through the WSDL proxy object and look through the function's outparams. Each technique will give you the same information. -

                        The results object contains more than the actual search results. It also contains information about the search itself, such as how long -it took and how many results were found (even though only 10 were returned). The Google web interface shows this information, -and you can access it programmatically too. -

                        Example 12.14. Accessing Secondary Information From Google

                        ->>> results.searchTime   
                        -0.224919
                        ->>> results.estimatedTotalResultsCount     
                        -29800000
                        ->>> results.directoryCategories            
                        -[<SOAPpy.Types.structType item at 14367400>:
                        - {'fullViewableName':
                        -  'Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark',
                        -  'specialEncoding': ''}]
                        ->>> results.directoryCategories[0].fullViewableName
                        -'Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark'
                        -
                        -
                          -
                        1. This search took 0.224919 seconds. That does not include the time spent sending and receiving the actual SOAP XML documents. It's just the time that Google spent processing your request once it received it. -
                        2. In total, there were approximately 30 million results. You can access them 10 at a time by changing the start parameter and calling server.doGoogleSearch again. -
                        3. For some queries, Google also returns a list of related categories in the Google Directory. You can append these URLs to http://directory.google.com/ to construct the link to the directory category page. -

                          12.8. Troubleshooting SOAP Web Services

                          -

                          Of course, the world of SOAP web services is not all happiness and light. Sometimes things go wrong. -

                          As you've seen throughout this chapter, SOAP involves several layers. There's the HTTP layer, since SOAP is sending XML documents to, and receiving XML documents from, an HTTP server. So all the debugging techniques you learned -in Chapter 11, HTTP Web Services come into play here. You can import httplib and then set httplib.HTTPConnection.debuglevel = 1 to see the underlying HTTP traffic. -

                          Beyond the underlying HTTP layer, there are a number of things that can go wrong. SOAPpy does an admirable job hiding the SOAP syntax from you, but that also means it can be difficult to determine where the problem is when things don't work. -

                          Here are a few examples of common mistakes that I've made in using SOAP web services, and the errors they generated. -

                          Example 12.15. Calling a Method With an Incorrectly Configured Proxy

                          ->>> from SOAPpy import SOAPProxy
                          ->>> url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'
                          ->>> server = SOAPProxy(url)    
                          ->>> server.getTemp('27502')    
                          -<Fault SOAP-ENV:Server.BadTargetObjectURI:
                          -Unable to determine object id from call: is the method element namespaced?>
                          -Traceback (most recent call last):
                          -  File "<stdin>", line 1, in ?
                          -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__
                          -    return self.__r_call(*args, **kw)
                          -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call
                          -    self.__hd, self.__ma)
                          -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call
                          -    raise p
                          -SOAPpy.Types.faultType: <Fault SOAP-ENV:Server.BadTargetObjectURI:
                          -Unable to determine object id from call: is the method element namespaced?>
                          -
                          -
                            -
                          1. Did you spot the mistake? You're creating a SOAPProxy manually, and you've correctly specified the service URL, but you haven't specified the namespace. Since multiple services may be routed through the same service URL, the namespace is essential to determine which service you're trying to talk to, and therefore which method you're really - calling. -
                          2. The server responds by sending a SOAP Fault, which SOAPpy turns into a Python exception of type SOAPpy.Types.faultType. All errors returned from any SOAP server will always be SOAP Faults, so you can easily catch this exception. In this case, the human-readable part of the SOAP Fault gives a clue to the problem: the method element is not namespaced, because the original SOAPProxy object was not configured with a service namespace. -

                            Misconfiguring the basic elements of the SOAP service is one of the problems that WSDL aims to solve. The WSDL file contains the service URL and namespace, so you can't get it wrong. Of course, there are still other things you can get wrong. -

                            Example 12.16. Calling a Method With the Wrong Arguments

                            ->>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
                            ->>> server = WSDL.Proxy(wsdlFile)
                            ->>> temperature = server.getTemp(27502)              
                            -<Fault SOAP-ENV:Server: Exception while handling service request:
                            -services.temperature.TempService.getTemp(int) -- no signature match>   
                            -Traceback (most recent call last):
                            -  File "<stdin>", line 1, in ?
                            -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__
                            -    return self.__r_call(*args, **kw)
                            -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call
                            -    self.__hd, self.__ma)
                            -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call
                            -    raise p
                            -SOAPpy.Types.faultType: <Fault SOAP-ENV:Server: Exception while handling service request:
                            -services.temperature.TempService.getTemp(int) -- no signature match>
                            -
                            -
                              -
                            1. Did you spot the mistake? It's a subtle one: you're calling server.getTemp with an integer instead of a string. As you saw from introspecting the WSDL file, the getTemp() SOAP function takes a single argument, zipcode, which must be a string. WSDL.Proxy will not coerce datatypes for you; you need to pass the exact datatypes that the server expects. -
                            2. Again, the server returns a SOAP Fault, and the human-readable part of the error gives a clue as to the problem: you're calling a getTemp function with an integer value, but there is no function defined with that name that takes an integer. In theory, SOAP allows you to overload functions, so you could have two functions in the same SOAP service with the same name and the same number of arguments, but the arguments were of different datatypes. This is why - it's important to match the datatypes exactly, and why WSDL.Proxy doesn't coerce datatypes for you. If it did, you could end up calling a completely different function! Good luck debugging - that one. It's much easier to be picky about datatypes and fail as quickly as possible if you get them wrong. -

                              It's also possible to write Python code that expects a different number of return values than the remote function actually returns. -

                              Example 12.17. Calling a Method and Expecting the Wrong Number of Return Values

                              ->>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
                              ->>> server = WSDL.Proxy(wsdlFile)
                              ->>> (city, temperature) = server.getTemp(27502)  
                              -Traceback (most recent call last):
                              -  File "<stdin>", line 1, in ?
                              -TypeError: unpack non-sequence
                              -
                              -
                                -
                              1. Did you spot the mistake? server.getTemp only returns one value, a float, but you've written code that assumes you're getting two values and trying to assign them - to two different variables. Note that this does not fail with a SOAP fault. As far as the remote server is concerned, nothing went wrong at all. The error only occurred after the SOAP transaction was complete, WSDL.Proxy returned a float, and your local Python interpreter tried to accomodate your request to split it into two different variables. Since the function only returned - one value, you get a Python exception trying to split it, not a SOAP Fault. -

                                What about Google's web service? The most common problem I've had with it is that I forget to set the application key properly. -

                                Example 12.18. Calling a Method With An Application-Specific Error

                                ->>> from SOAPpy import WSDL
                                ->>> server = WSDL.Proxy(r'/path/to/local/GoogleSearch.wsdl')
                                ->>> results = server.doGoogleSearch('foo', 'mark', 0, 10, False, "", 
                                -...    False, "", "utf-8", "utf-8")
                                -<Fault SOAP-ENV:Server:          
                                - Exception from service object: Invalid authorization key: foo:
                                - <SOAPpy.Types.structType detail at 14164616>:
                                - {'stackTrace':
                                -  'com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo
                                -   at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
                                -     QueryLimits.java:220)
                                -   at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127)
                                -   at com.google.soap.search.GoogleSearchService.doPublicMethodChecks(
                                -     GoogleSearchService.java:825)
                                -   at com.google.soap.search.GoogleSearchService.doGoogleSearch(
                                -     GoogleSearchService.java:121)
                                -   at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source)
                                -   at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
                                -   at java.lang.reflect.Method.invoke(Unknown Source)
                                -   at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
                                -   at org.apache.soap.providers.RPCJavaProvider.invoke(
                                -     RPCJavaProvider.java:129)
                                -   at org.apache.soap.server.http.RPCRouterServlet.doPost(
                                -     RPCRouterServlet.java:288)
                                -   at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
                                -   at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
                                -   at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237)
                                -   at com.google.gse.HttpConnection.run(HttpConnection.java:195)
                                -   at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201)
                                -Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size.
                                -   at com.google.soap.search.UserKey.<init>(UserKey.java:59)
                                -   at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
                                -     QueryLimits.java:217)
                                -   ... 14 more
                                -'}>
                                -Traceback (most recent call last):
                                -  File "<stdin>", line 1, in ?
                                -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__
                                -    return self.__r_call(*args, **kw)
                                -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call
                                -    self.__hd, self.__ma)
                                -  File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call
                                -    raise p
                                -SOAPpy.Types.faultType: <Fault SOAP-ENV:Server: Exception from service object:
                                -Invalid authorization key: foo:
                                -<SOAPpy.Types.structType detail at 14164616>:
                                -{'stackTrace':
                                -  'com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo
                                -   at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
                                -     QueryLimits.java:220)
                                -   at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127)
                                -   at com.google.soap.search.GoogleSearchService.doPublicMethodChecks(
                                -     GoogleSearchService.java:825)
                                -   at com.google.soap.search.GoogleSearchService.doGoogleSearch(
                                -     GoogleSearchService.java:121)
                                -   at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source)
                                -   at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
                                -   at java.lang.reflect.Method.invoke(Unknown Source)
                                -   at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
                                -   at org.apache.soap.providers.RPCJavaProvider.invoke(
                                -     RPCJavaProvider.java:129)
                                -   at org.apache.soap.server.http.RPCRouterServlet.doPost(
                                -     RPCRouterServlet.java:288)
                                -   at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
                                -   at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
                                -   at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237)
                                -   at com.google.gse.HttpConnection.run(HttpConnection.java:195)
                                -   at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201)
                                -Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size.
                                -   at com.google.soap.search.UserKey.<init>(UserKey.java:59)
                                -   at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
                                -     QueryLimits.java:217)
                                -   ... 14 more
                                -'}>
                                -
                                -
                                  -
                                1. Can you spot the mistake? There's nothing wrong with the calling syntax, or the number of arguments, or the datatypes. The - problem is application-specific: the first argument is supposed to be my application key, but foo is not a valid Google key. -
                                2. The Google server responds with a SOAP Fault and an incredibly long error message, which includes a complete Java stack trace. Remember that all SOAP errors are signified by SOAP Faults: errors in configuration, errors in function arguments, and application-specific errors like this. Buried in there - somewhere is the crucial piece of information: Invalid authorization key: foo. -
                                  -

                                  Further Reading on Troubleshooting SOAP

                                  - -

                                  12.9. Summary

                                  -

                                  SOAP web services are very complicated. The specification is very ambitious and tries to cover many different use cases for web - services. This chapter has touched on some of the simpler use cases. -

                                  -

                                  Before diving into the next chapter, make sure you're comfortable doing all of these things: -

                                  -
                                    -
                                  • Connecting to a SOAP server and calling remote methods - -
                                  • Loading a WSDL file and introspecting remote methods - -
                                  • Debugging SOAP calls with wire traces - -
                                  • Troubleshooting common SOAP-related errors -

                                  Chapter 13. Unit Testing

                                  @@ -6503,265 +5849,6 @@ numerals. You saw the mechanics of constructing and validating Roman numerals in
                                3. This site has more on Roman numerals, including a fascinating history of how Romans and other civilizations really used them (short answer: haphazardly and inconsistently). -

                                  13.2. Diving in

                                  -

                                  Now that you've completely defined the behavior you expect from your conversion functions, you're going to do something a - little unexpected: you're going to write a test suite that puts these functions through their paces and makes sure that they - behave the way you want them to. You read that right: you're going to write code that tests code that you haven't written - yet. -

                                  This is called unit testing, since the set of two conversion functions can be written and tested as a unit, separate from -any larger program they may become part of later. Python has a framework for unit testing, the appropriately-named unittest module. - - -", "
                                  Noteunittest is included with Python 2.1 and later. Python 2.0 users can download it from pyunit.sourceforge.net. -

                                  Unit testing is an important part of an overall testing-centric development strategy. If you write unit tests, it is important -to write them early (preferably before writing the code that they test), and to keep them updated as code and requirements -change. Unit testing is not a replacement for higher-level functional or system testing, but it is important in all phases -of development: -

                                  -
                                    -
                                  • Before writing code, it forces you to detail your requirements in a useful fashion. -
                                  • While writing code, it keeps you from over-coding. When all the test cases pass, the function is complete. -
                                  • When refactoring code, it assures you that the new version behaves the same way as the old version. -
                                  • When maintaining code, it helps you cover your ass when someone comes screaming that your latest change broke their old code. - (“But sir, all the unit tests passed when I checked it in...”) - -
                                  • When writing code in a team, it increases confidence that the code you're about to commit isn't going to break other peoples' - code, because you can run their unittests first. (I've seen this sort of thing in code sprints. A team breaks up the assignment, - everybody takes the specs for their task, writes unit tests for it, then shares their unit tests with the rest of the team. - That way, nobody goes off too far into developing code that won't play well with others.) - -
                                  -

                                  13.3. Introducing romantest.py

                                  -

                                  This is the complete test suite for your Roman numeral conversion functions, which are yet to be written but will eventually - be in roman.py. It is not immediately obvious how it all fits together; none of these classes or methods reference any of the others. - There are good reasons for this, as you'll see shortly. -

                                  Example 13.1. romantest.py

                                  -

                                  If you have not already done so, you can download this and other examples used in this book. -

                                  
                                  -"""Unit test for roman.py"""
                                  -
                                  -import roman
                                  -import unittest
                                  -
                                  -class KnownValues(unittest.TestCase):        
                                  -    knownValues = ( (1, 'I'),
                                  -  (2, 'II'),
                                  -  (3, 'III'),
                                  -  (4, 'IV'),
                                  -  (5, 'V'),
                                  -  (6, 'VI'),
                                  -  (7, 'VII'),
                                  -  (8, 'VIII'),
                                  -  (9, 'IX'),
                                  -  (10, 'X'),
                                  -  (50, 'L'),
                                  -  (100, 'C'),
                                  -  (500, 'D'),
                                  -  (1000, 'M'),
                                  -  (31, 'XXXI'),
                                  -  (148, 'CXLVIII'),
                                  -  (294, 'CCXCIV'),
                                  -  (312, 'CCCXII'),
                                  -  (421, 'CDXXI'),
                                  -  (528, 'DXXVIII'),
                                  -  (621, 'DCXXI'),
                                  -  (782, 'DCCLXXXII'),
                                  -  (870, 'DCCCLXX'),
                                  -  (941, 'CMXLI'),
                                  -  (1043, 'MXLIII'),
                                  -  (1110, 'MCX'),
                                  -  (1226, 'MCCXXVI'),
                                  -  (1301, 'MCCCI'),
                                  -  (1485, 'MCDLXXXV'),
                                  -  (1509, 'MDIX'),
                                  -  (1607, 'MDCVII'),
                                  -  (1754, 'MDCCLIV'),
                                  -  (1832, 'MDCCCXXXII'),
                                  -  (1993, 'MCMXCIII'),
                                  -  (2074, 'MMLXXIV'),
                                  -  (2152, 'MMCLII'),
                                  -  (2212, 'MMCCXII'),
                                  -  (2343, 'MMCCCXLIII'),
                                  -  (2499, 'MMCDXCIX'),
                                  -  (2574, 'MMDLXXIV'),
                                  -  (2646, 'MMDCXLVI'),
                                  -  (2723, 'MMDCCXXIII'),
                                  -  (2892, 'MMDCCCXCII'),
                                  -  (2975, 'MMCMLXXV'),
                                  -  (3051, 'MMMLI'),
                                  -  (3185, 'MMMCLXXXV'),
                                  -  (3250, 'MMMCCL'),
                                  -  (3313, 'MMMCCCXIII'),
                                  -  (3408, 'MMMCDVIII'),
                                  -  (3501, 'MMMDI'),
                                  -  (3610, 'MMMDCX'),
                                  -  (3743, 'MMMDCCXLIII'),
                                  -  (3844, 'MMMDCCCXLIV'),
                                  -  (3888, 'MMMDCCCLXXXVIII'),
                                  -  (3940, 'MMMCMXL'),
                                  -  (3999, 'MMMCMXCIX'))     
                                  -
                                  -    def testToRomanKnownValues(self):        
                                  -        """to_roman should give known result with known input"""
                                  -        for integer, numeral in self.knownValues:              
                                  -            result = roman.to_roman(integer)  
                                  -            self.assertEqual(numeral, result)
                                  -
                                  -    def testFromRomanKnownValues(self):        
                                  -        """from_roman should give known result with known input"""
                                  -        for integer, numeral in self.knownValues:                
                                  -            result = roman.from_roman(numeral)  
                                  -            self.assertEqual(integer, result)  
                                  -
                                  -class ToRomanBadInput(unittest.TestCase):          
                                  -    def testTooLarge(self):      
                                  -        """to_roman should fail with large input""" 
                                  -        self.assertRaises(roman.OutOfRangeError, roman.to_roman, 4000)
                                  -
                                  -    def testZero(self):          
                                  -        """to_roman should fail with 0 input"""     
                                  -        self.assertRaises(roman.OutOfRangeError, roman.to_roman, 0)   
                                  -
                                  -    def testNegative(self):      
                                  -        """to_roman should fail with negative input"""                
                                  -        self.assertRaises(roman.OutOfRangeError, roman.to_roman, -1)  
                                  -
                                  -    def testNonInteger(self):    
                                  -        """to_roman should fail with non-integer input"""             
                                  -        self.assertRaises(roman.NotIntegerError, roman.to_roman, 0.5) 
                                  -
                                  -class FromRomanBadInput(unittest.TestCase):  
                                  -    def testTooManyRepeatedNumerals(self):   
                                  -        """from_roman should fail with too many repeated numerals"""              
                                  -        for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):             
                                  -            self.assertRaises(roman.InvalidRomanNumeralError, roman.from_roman, s)
                                  -
                                  -    def testRepeatedPairs(self):             
                                  -        """from_roman should fail with repeated pairs of numerals"""              
                                  -        for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):               
                                  -            self.assertRaises(roman.InvalidRomanNumeralError, roman.from_roman, s)
                                  -
                                  -    def testMalformedAntecedent(self):       
                                  -        """from_roman should fail with malformed antecedents""" 
                                  -        for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
                                  -'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):     
                                  -            self.assertRaises(roman.InvalidRomanNumeralError, roman.from_roman, s)
                                  -
                                  -class SanityCheck(unittest.TestCase):        
                                  -    def testSanity(self):  
                                  -        """from_roman(to_roman(n))==n for all n"""
                                  -        for integer in range(1, 4000):       
                                  -            numeral = roman.to_roman(integer) 
                                  -            result = roman.from_roman(numeral)
                                  -            self.assertEqual(integer, result)
                                  -
                                  -class CaseCheck(unittest.TestCase): 
                                  -    def testToRomanCase(self):      
                                  -        """to_roman should always return uppercase"""  
                                  -        for integer in range(1, 4000):                
                                  -            numeral = roman.to_roman(integer)          
                                  -            self.assertEqual(numeral, numeral.upper())
                                  -
                                  -    def testFromRomanCase(self):    
                                  -        """from_roman should only accept uppercase input"""
                                  -        for integer in range(1, 4000):                
                                  -            numeral = roman.to_roman(integer)          
                                  -            roman.from_roman(numeral.upper())          
                                  -            self.assertRaises(roman.InvalidRomanNumeralError,
                                  -            roman.from_roman, numeral.lower())
                                  -
                                  -if __name__ == "__main__":
                                  -    unittest.main()   
                                  -

                                  Further reading

                                  - -

                                  13.4. Testing for success

                                  -

                                  A test case answers a single question about the code it is testing. A test case should be able to... -

                                    -
                                  • ...run completely by itself, without any human input. Unit testing is about automation. -
                                  • ...determine by itself whether the function it is testing has passed or failed, without a human interpreting the results. -
                                  • ...run in isolation, separate from any other test cases (even if they test the same functions). Each test case is an island. -
                                  -

                                  Given that, let's build the first test case. You have the following requirement: -

                                  -
                                    -
                                  1. to_roman() should return the Roman numeral representation for all integers 1 to 3999. - -
                                  -

                                  Example 13.2. testToRomanKnownValues

                                  
                                  -class KnownValues(unittest.TestCase):         
                                  -    knownValues = ( (1, 'I'),
                                  -  (2, 'II'),
                                  -  (3, 'III'),
                                  -  (4, 'IV'),
                                  -  (5, 'V'),
                                  -  (6, 'VI'),
                                  -  (7, 'VII'),
                                  -  (8, 'VIII'),
                                  -  (9, 'IX'),
                                  -  (10, 'X'),
                                  -  (50, 'L'),
                                  -  (100, 'C'),
                                  -  (500, 'D'),
                                  -  (1000, 'M'),
                                  -  (31, 'XXXI'),
                                  -  (148, 'CXLVIII'),
                                  -  (294, 'CCXCIV'),
                                  -  (312, 'CCCXII'),
                                  -  (421, 'CDXXI'),
                                  -  (528, 'DXXVIII'),
                                  -  (621, 'DCXXI'),
                                  -  (782, 'DCCLXXXII'),
                                  -  (870, 'DCCCLXX'),
                                  -  (941, 'CMXLI'),
                                  -  (1043, 'MXLIII'),
                                  -  (1110, 'MCX'),
                                  -  (1226, 'MCCXXVI'),
                                  -  (1301, 'MCCCI'),
                                  -  (1485, 'MCDLXXXV'),
                                  -  (1509, 'MDIX'),
                                  -  (1607, 'MDCVII'),
                                  -  (1754, 'MDCCLIV'),
                                  -  (1832, 'MDCCCXXXII'),
                                  -  (1993, 'MCMXCIII'),
                                  -  (2074, 'MMLXXIV'),
                                  -  (2152, 'MMCLII'),
                                  -  (2212, 'MMCCXII'),
                                  -  (2343, 'MMCCCXLIII'),
                                  -  (2499, 'MMCDXCIX'),
                                  -  (2574, 'MMDLXXIV'),
                                  -  (2646, 'MMDCXLVI'),
                                  -  (2723, 'MMDCCXXIII'),
                                  -  (2892, 'MMDCCCXCII'),
                                  -  (2975, 'MMCMLXXV'),
                                  -  (3051, 'MMMLI'),
                                  -  (3185, 'MMMCLXXXV'),
                                  -  (3250, 'MMMCCL'),
                                  -  (3313, 'MMMCCCXIII'),
                                  -  (3408, 'MMMCDVIII'),
                                  -  (3501, 'MMMDI'),
                                  -  (3610, 'MMMDCX'),
                                  -  (3743, 'MMMDCCXLIII'),
                                  -  (3844, 'MMMDCCCXLIV'),
                                  -  (3888, 'MMMDCCCLXXXVIII'),
                                  -  (3940, 'MMMCMXL'),
                                  -  (3999, 'MMMCMXCIX'))      
                                  -
                                  -    def testToRomanKnownValues(self):         
                                  -        """to_roman should give known result with known input"""
                                  -        for integer, numeral in self.knownValues:              
                                  -            result = roman.to_roman(integer)    
                                  -            self.assertEqual(numeral, result) 

                                  13.5. Testing for failure

                                  It is not enough to test that functions succeed when given good input; you must also test that they fail when given bad input. And not just any sort of failure; they must fail in the way you expect.

                                  Remember the other requirements for to_roman(): diff --git a/dip3.css b/dip3.css index 6d41645..0abc379 100644 --- a/dip3.css +++ b/dip3.css @@ -37,14 +37,9 @@ pre a:hover{border:0} kbd{font-weight:bold} .prompt{color:#667} -/* headers */ -h1,h2,h3,p,ul,ol{margin:1.75em 0;font-size:medium} -h1{background:papayawhip;width:100%} -h1,h2,h3{clear:both} - /* tables */ table{width:100%;border-collapse:collapse} -th,td{width:45%;margin:0;padding:0 0.5em} +th,td{width:45%;margin:0;padding:0 0.5em;border:1px solid #bbb} th{text-align:left;vertical-align:baseline} td{vertical-align:top} th:first-child{width:10%;text-align:center} @@ -52,7 +47,10 @@ th:first-child{width:10%;text-align:center} .hover{background:#eee;color:inherit;cursor:default} td pre{margin:0;padding:0;border:0} -/* section counters */ +/* headers */ +h1,h2,h3,p,ul,ol{margin:1.75em 0;font-size:medium} +h1,#noscript{background:papayawhip;width:100%} +h1,h2,h3{clear:both} body{counter-reset:h1} h1:before{content:"Chapter " counter(h1) ". "} h1{counter-reset:h2} diff --git a/dip3.js b/dip3.js index 0c0d597..c4b6ec0 100644 --- a/dip3.js +++ b/dip3.js @@ -4,6 +4,7 @@ var LANGS = {'python2': 'Python 2', 'java': 'Java', 'perl5': 'Perl 5', 'clang': //google.load("jquery", "1.3"); //google.setOnLoadCallback(function() { $(document).ready(function() { + $("#noscript").hide(); var HS = {'visible': 'hide', 'hidden': 'show'}; /* // toggle-able language comparisons diff --git a/jquery.js b/jquery.js index 96095bc..9263574 100644 --- a/jquery.js +++ b/jquery.js @@ -1,1255 +1,1271 @@ -/*! - * jQuery JavaScript Library v1.3.1 - * http://jquery.com/ - * - * Copyright (c) 2009 John Resig - * Dual licensed under the MIT and GPL licenses. - * http://docs.jquery.com/License - * - * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009) - * Revision: 6158 - */ -(function(){ - -var - // Will speed up references to window, and allows munging its name. - window = this, - // Will speed up references to undefined, and allows munging its name. - undefined, - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - // Map over the $ in case of overwrite - _$ = window.$, - - jQuery = window.jQuery = window.$ = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); - }, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - this.context = selector; - return this; - } - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) - selector = jQuery.clean( [ match[1] ], context ); - - // HANDLE: $("#id") - else { - var elem = document.getElementById( match[3] ); - - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem && elem.id != match[3] ) - return jQuery().find( selector ); - - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem || [] ); - ret.context = document; - ret.selector = selector; - return ret; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return jQuery( context ).find( selector ); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return jQuery( document ).ready( selector ); - - // Make sure that old selector state is passed along - if ( selector.selector && selector.context ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return this.setArray(jQuery.makeArray(selector)); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.3.1", - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num === undefined ? - - // Return a 'clean' array - jQuery.makeArray( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) - ret.selector = this.selector + (this.selector ? " " : "") + selector; - else if ( name ) - ret.selector = this.selector + "." + name + "(" + selector + ")"; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem && elem.jquery ? elem[0] : elem - , this ); - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( typeof name === "string" ) - if ( value === undefined ) - return this[0] && jQuery[ type || "attr" ]( this[0], name ); - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text !== "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).clone(); - - if ( this[0].parentNode ) - wrap.insertBefore( this[0] ); - - wrap.map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }).append(this); - } - - return this; - }, - - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - return this.prevObject || jQuery( [] ); - }, - - // For internal use only. - // Behaves like an Array's .push method, not like a jQuery method. - push: [].push, - - find: function( selector ) { - if ( this.length === 1 && !/,/.test(selector) ) { - var ret = this.pushStack( [], "find", selector ); - ret.length = 0; - jQuery.find( selector, this[0], ret ); - return ret; - } else { - var elems = jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - }); - - return this.pushStack( /[^+>] [^+>]/.test( selector ) ? - jQuery.unique( elems ) : - elems, "find", selector ); - } - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var clone = this.cloneNode(true), - container = document.createElement("div"); - container.appendChild(clone); - return jQuery.clean([container.innerHTML])[0]; - } else - return this.cloneNode(true); - }); - - // Need to set the expando to null on the cloned set if it exists - // removeData doesn't work here, IE removes it from the original as well - // this is primarily for IE but the data expando shouldn't be copied over in any browser - var clone = ret.find("*").andSelf().each(function(){ - if ( this[ expando ] !== undefined ) - this[ expando ] = null; - }); - - // Copy the events from the original to the clone - if ( events === true ) - this.find("*").andSelf().each(function(i){ - if (this.nodeType == 3) - return; - var events = jQuery.data( this, "events" ); - - for ( var type in events ) - for ( var handler in events[ type ] ) - jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); - }); - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ - return elem.nodeType === 1; - }) ), "filter", selector ); - }, - - closest: function( selector ) { - var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null; - - return this.map(function(){ - var cur = this; - while ( cur && cur.ownerDocument ) { - if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) - return cur; - cur = cur.parentNode; - } - }); - }, - - not: function( selector ) { - if ( typeof selector === "string" ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - return this.pushStack( jQuery.unique( jQuery.merge( - this.get(), - typeof selector === "string" ? - jQuery( selector ) : - jQuery.makeArray( selector ) - ))); - }, - - is: function( selector ) { - return !!selector && jQuery.multiFilter( selector, this ).length > 0; - }, - - hasClass: function( selector ) { - return !!selector && this.is( "." + selector ); - }, - - val: function( value ) { - if ( value === undefined ) { - var elem = this[0]; - - if ( elem ) { - if( jQuery.nodeName( elem, 'option' ) ) - return (elem.attributes.value || {}).specified ? elem.value : elem.text; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - } - - // Everything else, we just grab the value - return (elem.value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - if ( typeof value === "number" ) - value += ''; - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(value); - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - return value === undefined ? - (this[0] ? - this[0].innerHTML : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - return this.after( value ).remove(); - }, - - eq: function( i ) { - return this.slice( i, +i + 1 ); - }, - - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ), - "slice", Array.prototype.slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - return this.add( this.prevObject ); - }, - - domManip: function( args, table, callback ) { - if ( this[0] ) { - var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), - scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), - first = fragment.firstChild, - extra = this.length > 1 ? fragment.cloneNode(true) : fragment; - - if ( first ) - for ( var i = 0, l = this.length; i < l; i++ ) - callback.call( root(this[i], first), i > 0 ? extra.cloneNode(true) : fragment ); - - if ( scripts ) - jQuery.each( scripts, evalScript ); - } - - return this; - - function root( elem, cur ) { - return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; - } - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -function now(){ - return +new Date; -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - var src = target[ name ], copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) - continue; - - // Recurse if we're merging object values - if ( deep && copy && typeof copy === "object" && !copy.nodeType ) - target[ name ] = jQuery.extend( deep, - // Never move original objects, clone them - src || ( copy.length != null ? [ ] : { } ) - , copy ); - - // Don't bring in undefined values - else if ( copy !== undefined ) - target[ name ] = copy; - - } - - // Return the modified object - return target; -}; - -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, - // cache defaultView - defaultView = document.defaultView || {}, - toString = Object.prototype.toString; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return toString.call(obj) === "[object Function]"; - }, - - isArray: function( obj ) { - return toString.call(obj) === "[object Array]"; - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - data = jQuery.trim( data ); - - if ( data ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.support.scriptEval ) - script.appendChild( document.createTextNode( data ) ); - else - script.text = data; - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, length = object.length; - - if ( args ) { - if ( length === undefined ) { - for ( name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( ; i < length; ) - if ( callback.apply( object[ i++ ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( length === undefined ) { - for ( name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames !== undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use hasClass("class") - has: function( elem, className ) { - return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - var padding = 0, border = 0; - jQuery.each( which, function() { - padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - val -= Math.round(padding + border); - } - - if ( jQuery(elem).is(":visible") ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, val); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - var ret, style = elem.style; - - // We need to handle opacity special in IE - if ( name == "opacity" && !jQuery.support.opacity ) { - ret = jQuery.attr( style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && style && style[ name ] ) - ret = style[ name ]; - - else if ( defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var computedStyle = defaultView.getComputedStyle( elem, null ); - - if ( computedStyle ) - ret = computedStyle.getPropertyValue( name ); - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var left = style.left, rsLeft = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - style.left = ret || 0; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - elem.runtimeStyle.left = rsLeft; - } - } - - return ret; - }, - - clean: function( elems, context, fragment ) { - context = context || document; - - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { - var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); - if ( match ) - return [ context.createElement( match[1] ) ]; - } - - var ret = [], scripts = [], div = context.createElement("div"); - - jQuery.each(elems, function(i, elem){ - if ( typeof elem === "number" ) - elem += ''; - - if ( !elem ) - return; - - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = jQuery.trim( elem ).toLowerCase(); - - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
                                  " ] || - - !tags.indexOf("

                                  " ] || - - // matched above - (!tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - // IE can't serialize and - + + diff --git a/porting-code-to-python-3-with-2to3.html b/porting-code-to-python-3-with-2to3.html index b7fbebf..cbd8d49 100644 --- a/porting-code-to-python-3-with-2to3.html +++ b/porting-code-to-python-3-with-2to3.html @@ -14,7 +14,7 @@ h3:before{counter-increment:h3;content:"A." counter(h2) "." counter(h3) ". "}

                                  skip to main content

                                  -

                                  Porting code to Python 3 with 2to3

                                  Life is pleasant. Death is peaceful. It’s the transition that’s troublesome.
                                  — Isaac Asimov (attributed) @@ -81,6 +81,7 @@ h3:before{counter-increment:h3;content:"A." counter(h2) "." counter(h3) ". "}

                                  Virtually all Python 2 programs will need at least some tweaking to run properly under Python 3. To help with this transition, Python 3 comes with a utility script called 2to3, which takes your actual Python 2 source code as input and auto-converts as much as it can to Python 3. Case study: porting chardet to Python 3 describes how to run the 2to3 script, then shows some things it can't fix automatically. This appendix documents what it can fix automatically.

                                  print statement

                                  In Python 2, print was a statement. Whatever you wanted to print simply followed the print keyword. In Python 3, print() is a function — whatever you want to print is passed to print() like any other function. +

                                  [The code examples will be easier to follow if you enable Javascript, but whatever.]

                                  skip over this table @@ -1272,5 +1273,5 @@ do_stuff(a_list)

                                  FIXME: once the rest of the book is written, this appendix should contain copious links back to any chapter or section that touches on these features.

                                  © 2001–4, 2009 ark Pilgrim, CC-BY-SA-3.0 - - + + diff --git a/push b/push new file mode 100644 index 0000000..29e533f --- /dev/null +++ b/push @@ -0,0 +1,6 @@ +#!/bin/sh + +ssh diveintomark.org "hg -R /home/mark/db/diveintopython3/ serve --stdio" & +pid=$! +hg push ssh://mark@diveintomark.org//home/mark/db/diveintopython3/ +kill "$pid" diff --git a/regular-expressions.html b/regular-expressions.html index e0501c5..776d247 100644 --- a/regular-expressions.html +++ b/regular-expressions.html @@ -12,7 +12,7 @@ body{counter-reset:h1 4}

                                  skip to main content

                                    
                                  -

                                  Regular expressions

                                  Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems.
                                  Jamie Zawinski @@ -42,6 +42,7 @@ body{counter-reset:h1 4}

                                  Case study: street addresses

                                  This series of examples was inspired by a real-life problem I had in my day job several years ago, when I needed to scrub and standardize street addresses exported from a legacy system before importing them into a newer system. (See, I don’t just make this stuff up; it’s actually useful.) This example shows how I approached the problem. +

                                  [The code examples will be easier to follow if you enable Javascript, but whatever.]

                                   >>> s = '100 NORTH MAIN ROAD'
                                   >>> s.replace('ROAD', 'RD.')                
                                  @@ -431,5 +432,5 @@ body{counter-reset:h1 4}
                                   
                                   

                                  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.

                                  © 2001–4, 2009 ark Pilgrim, CC-BY-SA-3.0 - - + + diff --git a/roman2.py b/roman2.py index c751dfc..d4c53e6 100644 --- a/roman2.py +++ b/roman2.py @@ -4,7 +4,8 @@ This program is part of "Dive Into Python 3", a free Python book for experienced programmers. Visit http://diveintopython3.org/ for the latest version. """ -class OutOfRangeError(ValueError): pass +class OutOfRangeError(ValueError): + pass roman_numeral_map = (('M', 1000), ('CM', 900), @@ -22,8 +23,8 @@ roman_numeral_map = (('M', 1000), def to_roman(n): """convert integer to Roman numeral""" -# if n > 3999: -# raise OutOfRangeError("number out of range (must be less than 3999)") + if n > 3999: + raise OutOfRangeError("number out of range (must be less than 3999)") result = "" for numeral, integer in roman_numeral_map: diff --git a/table-of-contents.html b/table-of-contents.html index 615e739..c8d668c 100644 --- a/table-of-contents.html +++ b/table-of-contents.html @@ -26,7 +26,7 @@ ul li ol{margin:0;padding:0 0 0 2.5em}

                                4. Python from source
                                5. The interactive shell -
                                6. Your first Python program +
                                7. Your first Python program
                                  1. Diving in
                                  2. Declaring functions @@ -45,7 +45,7 @@ ul li ol{margin:0;padding:0 0 0 2.5em}
                                  3. Running scripts
                                  4. Further reading
                                  -
                                8. Native Python datatypes +
                                9. Native Python datatypes
                                  1. Diving in
                                  2. Booleans @@ -74,7 +74,7 @@ ul li ol{margin:0;padding:0 0 0 2.5em}
                                  3. Byte streams
                                  4. Summary
                                  -
                                10. Regular expressions +
                                11. Regular expressions
                                  1. Diving in
                                  2. Case study: street addresses @@ -123,11 +123,12 @@ ul li ol{margin:0;padding:0 0 0 2.5em}
                                  3. ...mention why from module import * is only allowed at module level
                                  -
                                12. Unit testing +
                                13. Unit testing
                                  1. (Not) diving in -
                                  2. romantest1.py -
                                  3. romantest2.py +
                                  4. A single question +
                                  5. “Halt and catch fire” +
                                  6. More halting, more fire
                                  7. ...
                                14. Test-first programming @@ -248,7 +249,7 @@ ul li ol{margin:0;padding:0 0 0 2.5em}
                                15. PyPy
                                16. Stackless Python -
                                17. Case study: porting chardet to Python 3 +
                                18. Case study: porting chardet to Python 3
                                  1. Introducing chardet: a mini-FAQ
                                      @@ -278,7 +279,7 @@ ul li ol{margin:0;padding:0 0 0 2.5em}
                                  -

                                  romantest1.py

                                  +

                                  A single question

                                  A test case answers a single question about the code it is testing. A test case should be able to...

                                  • ...run completely by itself, without any human input. Unit testing is about automation. @@ -58,6 +59,7 @@ body{counter-reset:h1 7}
                                  • The to_roman() function should return the Roman numeral representation for all integers 1 to 3999.

                                    It is not immediately obvious how this code does… well, anything. It defines a class which has no __init__() method. The class does have another method, but it is never called. The entire script has a __main__ block, but it doesn't reference the class or its method. But it does do something, I promise. +

                                    [The code examples will be easier to follow if you enable Javascript, but whatever.]

                                    [download romantest1.py]

                                    import roman1
                                     import unittest
                                    @@ -224,30 +226,118 @@ OK
                                  • Hooray! The to_roman() function passes the “known values” test case. It's not comprehensive, but it does put the function through its paces with a variety of inputs, including inputs that produce every single-character Roman numeral, the largest possible input (3999), and the input that produces the longest possible Roman numeral (3888). At this point, you can be reasonably confident that the function works for any good input value you could throw at it.

                                    “Good” input? Hmm. What about bad input? -

                                    romantest2.py

                                    +

                                    “Halt and catch fire”

                                    It is not enough to test that functions succeed when given good input; you must also test that they fail when given bad input. And not just any sort of failure; they must fail in the way you expect.

                                     >>> import roman1
                                    ->>> roman1.to_roman(4000)  
                                    +>>> roman1.to_roman(4000)
                                     'MMMM'
                                     >>> roman1.to_roman(5000)
                                     'MMMMM'
                                    ->>> roman1.to_roman(9999)
                                    -'MMMMMMMMMCMXCIX'
                                    +>>> roman1.to_roman(9000) +'MMMMMMMMM'
                                  1. -
                                  2. FIXME +
                                  3. That's definitely not what you wanted — that's not even a valid Roman numeral! In fact, each of these numbers is outside the range of acceptable input, but the function returns a bogus value anyway. Silently returning bad values is baaaaaaad; if a program is going to fail, it is far better that it fail quickly and noisily. “Halt and catch fire,” as the saying goes. The Pythonic way to halt and catch fire is to raise an exception.

                                  The question to ask yourself is, “How can I express this as a testable requirement?” How's this for starters:

                                  -

                                  The to_roman() function should fail when given an integer greater than 3999. +

                                  The to_roman() function should raise an OutOfRangeError when given an integer greater than 3999.

                                  What would that test look like?

                                  [download romantest2.py] -

                                  class ToRomanBadInput(unittest.TestCase):
                                  -    def test_too_large(self):
                                  +
                                  
                                  +class ToRomanBadInput(unittest.TestCase):                                 
                                  +    def test_too_large(self):                                             
                                           """to_roman should fail with large input"""
                                  -        self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000)
                                  - + self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000)
                                  +
                                    +
                                  1. Like the previous test case, you create a class that inherits from unittest.TestCase. You can have more than one test per class (as you'll see later in this chapter), but I chose to create a new class here because this test is something different than the last one. We'll keep all the good input tests together in one class, and all the bad input tests together in another. +
                                  2. Like the previous test case, the test itself is a method of the class, with a name starting with test. +
                                  3. The unittest.TestCase class provides the assertRaises method, which takes the following arguments: the exception you're expecting, the function you're testing, and the arguments you're passing to that function. (If the function you're testing takes more than one argument, pass them all to assertRaises, in order, and it will pass them right along to the function you're testing.) +
                                  +

                                  Pay close attention to this last line of code. Instead of calling to_roman() directly and manually checking that it raises a particular exception (by wrapping it in a try...except block [FIXME xref]), the assertRaises method has encapsulated all of that for us. All you do is tell it what exception you're expecting (roman2.OutOfRangeError), the function (to_roman()), and the function's arguments (4000). The assertRaises method takes care of calling to_roman() and checking that it raises roman2.OutOfRangeError. +

                                  Also note that you're passing the to_roman() function itself as an argument; you're not calling it, and you're not passing the name of it as a string. Have I mentioned recently how handy it is that everything in Python is an object? +

                                  So what happens when you run the test suite with this new test? +

                                  +you@localhost:~$ python3 romantest2.py -v
                                  +to_roman should give known result with known input ... ok
                                  +to_roman should fail with large input ... ERROR                         
                                  +
                                  +======================================================================
                                  +ERROR: to_roman should fail with large input                          
                                  +----------------------------------------------------------------------
                                  +Traceback (most recent call last):
                                  +  File "romantest2.py", line 78, in test_too_large
                                  +    self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000)
                                  +AttributeError: 'module' object has no attribute 'OutOfRangeError'      
                                  +
                                  +----------------------------------------------------------------------
                                  +Ran 2 tests in 0.000s
                                  +
                                  +FAILED (errors=1)
                                  +
                                    +
                                  1. You should have expected this to fail (since you haven't written any code to pass it yet), but... it didn't actually “fail,” it had an “error” instead. This is a subtle but important distinction. A unit test actually has three return values: pass, fail, and error. Pass, of course, means that the test passed — the code did what you expected. “Fail” is what the previous test case did (until you wrote code to make it pass) — it executed the code but the result was not what you expected. “Error” means that the code didn't even execute properly. +
                                  2. Why didn't the code execute properly? The traceback gives the answer: the module you're testing doesn't have an exception called OutOfRangeError. Remember, you passed this exception to the assertRaises() method, because it's the exception you want the function to raise given an out-of-range input. But the exception doesn't exist, so the call to the assertRaises() method failed. It never got a chance to test the to_roman() function; it didn't get that far. +
                                  +

                                  To solve this problem, you need to define the OutOfRangeError exception in roman2.py. +

                                  class OutOfRangeError(ValueError):  
                                  +    pass                            
                                  +
                                    +
                                  1. Exceptions are classes. An “out of range” error is a kind of value error — the argument value is out of its acceptable range. So this exception inherits from the built-in ValueError exception. This is not strictly necessary (it could just inherit from the base Exception class), but it feels right. +
                                  2. Exceptions don't actually do anything, but you need at least one line of code to make a class. Calling pass does precisely nothing, but it's a line of Python code, so that makes it a class. +
                                  +

                                  Now run the test suite again. +

                                  +you@localhost:~$ python3 romantest2.py -v
                                  +to_roman should give known result with known input ... ok
                                  +to_roman should fail with large input ... FAIL                          
                                  +
                                  +======================================================================
                                  +FAIL: to_roman should fail with large input
                                  +----------------------------------------------------------------------
                                  +Traceback (most recent call last):
                                  +  File "romantest2.py", line 78, in test_too_large
                                  +    self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000)
                                  +AssertionError: OutOfRangeError not raised by to_roman                 
                                  +
                                  +----------------------------------------------------------------------
                                  +Ran 2 tests in 0.016s
                                  +
                                  +FAILED (failures=1)
                                  +
                                    +
                                  1. The new test is still not passing, but it's not returning an error either. Instead, the test is failing. That's progress! It means the call to the assertRaises() method succeeded this time, and the unit test framework actually tested the to_roman() function. +
                                  2. Of course, the to_roman() function isn't raising the OutOfRangeError exception you just defined, because you haven't told it to do that yet. That's excellent news! It means this is a valid test case — it fails before you write the code to make it pass. +
                                  +

                                  Now you can write the code to make this test pass. +

                                  [download roman2.py] +

                                  def to_roman(n):
                                  +    """convert integer to Roman numeral"""
                                  +    if n > 3999:
                                  +        raise OutOfRangeError("number out of range (must be less than 3999)")  
                                  +
                                  +    result = ""
                                  +    for numeral, integer in roman_numeral_map:
                                  +        while n >= integer:
                                  +            result += numeral
                                  +            n -= integer
                                  +    return result
                                  +
                                    +
                                  1. This is straightforward: if the given input (n) is greater than 3999, raise an OutOfRangeError exception. The unit test does not check the human-readable string that accompanies the exception, although you could write another test that did check it (but watch out for internationalization issues for strings that vary by the user's language or environment). +
                                  +

                                  Does this make the test pass? Let's find out. +

                                  +you@localhost:~$ python3 romantest2.py -v
                                  +to_roman should give known result with known input ... ok
                                  +to_roman should fail with large input ... ok                            
                                  +
                                  +----------------------------------------------------------------------
                                  +Ran 2 tests in 0.000s
                                  +
                                  +OK
                                  +
                                    +
                                  1. Hooray! Both tests pass. Because you worked iteratively, bouncing back and forth between testing and coding, you can be sure that the two lines of code you just wrote were the cause of that one test going from “fail” to “pass.” That kind of confidence doesn't come cheap, but it will pay for itself over the lifetime of your code. +
                                  +

                                  More halting, more fire

                                  ...

                                  © 2001–4, 2009 ark Pilgrim, CC-BY-SA-3.0 - - + + diff --git a/your-first-python-program.html b/your-first-python-program.html index 55ee7d4..f2ef8ac 100644 --- a/your-first-python-program.html +++ b/your-first-python-program.html @@ -12,7 +12,7 @@ body{counter-reset:h1 1}

                                  skip to main content

                                    
                                  -

                                  Your first Python program

                                  Don’t bury your burden in saintly silence. You have a problem? Great. Rejoice, dive in, and investigate.
                                  Ven. Henepola Gunararatana @@ -40,6 +40,7 @@ body{counter-reset:h1 1}

                                  Diving in

                                  Books about programming usually start with a bunch of boring chapters about fundamentals and eventually work up to building something useful. Let's skip all that. Here is a complete, working Python program. It probably makes absolutely no sense to you. Don't worry about that, because you're going to dissect it line by line. But read through it first and see what, if anything, you can make of it. +

                                  [The code examples will be easier to follow if you enable Javascript, but whatever.]

                                  [download humansize.py]

                                  SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
                                               1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
                                  @@ -239,5 +240,5 @@ if __name__ == "__main__":
                                   
                                19. Python Reference Manual explains what it means to say that everything in Python is an object, because some people are pedantic and like to discuss that sort of thing at great length.

                                  © 2001–4, 2009 ark Pilgrim, CC-BY-SA-3.0 - - + +