Chapter 10. Scripts and Streams

Chapter 10. Scripts and Streams

10.1. Abstracting input sources

One of Python's greatest strengths is its dynamic binding, and one powerful use of dynamic binding is the file-like object.

Many functions which require an input source could simply take a filename, go open the file for reading, read it, and close it when they're done. But they don't. Instead, they take a file-like object.


This is how reading from real files works; the difference is that you're not limiting yourself to real files. The input source could be anything: a file on disk, a web page, even a hard-coded string. As long as you pass a file-like object to the function, and the function simply calls the object's read method, the function can handle any kind of input source without specific code to handle each kind.


Example 10.1. Parsing XML from a file

>>> from xml.dom import minidom
>>> fsock = open('binary.xml')    1
>>> xmldoc = minidom.parse(fsock) 2
>>> fsock.close()                 3
>>> print xmldoc.toxml()          4
<?xml version="1.0" ?>
<ref id="bit">
<ref id="byte">
1 First, you open the file on disk. This gives you a file object.
3 Be sure to call the close method of the file object after you're done with it. minidom.parse will not do this for you.
4 Calling the toxml() method on the returned XML document prints out the entire thing.

Well, that all seems like a colossal waste of time. After all, you've already seen that minidom.parse can simply take the filename and do all the opening and closing nonsense automatically. And it's true that if you know you're just going to be parsing a local file, you can pass the filename and minidom.parse is smart enough to Do The Right Thing™. But notice how similar -- and easy -- it is to parse an XML document straight from the Internet.

Example 10.2. Parsing XML from a URL

>>> import urllib
>>> usock = urllib.urlopen('') 1
>>> xmldoc = minidom.parse(usock)                              2
>>> usock.close()                                              3
>>> print xmldoc.toxml()                                       4
<?xml version="1.0" ?>
<rdf:RDF xmlns=""




1 As you saw in a previous chapter, urlopen takes a web page URL and returns a file-like object. Most importantly, this object has a read method which returns the HTML source of the web page.
2 Now you pass the file-like object to minidom.parse, which obediently calls the read method of the object and parses the XML data that the read method returns. The fact that this XML data is now coming straight from a web page is completely irrelevant. minidom.parse doesn't know about web pages, and it doesn't care about web pages; it just knows about file-like objects.
3 As soon as you're done with it, be sure to close the file-like object that urlopen gives you.
4 By the way, this URL is real, and it really is XML. It's an XML representation of the current headlines on Slashdot, a technical news and gossip site.

Example 10.3. Parsing XML from a string (the easy but inflexible way)

>>> xmldoc = minidom.parseString(contents) 1
>>> print xmldoc.toxml()
<?xml version="1.0" ?>

OK, so you can use the minidom.parse function for parsing both local files and remote URLs, but for parsing strings, you use... a different function. That means that if you want to be able to take input from a file, a URL, or a string, you'll need special logic to check whether it's a string, and call the parseString function instead. How unsatisfying.

If there were a way to turn a string into a file-like object, then you could simply pass this object to minidom.parse. And in fact, there is a module specifically designed for doing just that: StringIO.

Example 10.4. Introducing StringIO

>>> import StringIO
>>> ssock = StringIO.StringIO(contents)   1
>>>                          2
>>>                          3
>>>                         4
>>>                        5
'<grammar><ref i'
>>> ssock.close()                         6
2 Now you have a file-like object, and you can do all sorts of file-like things with it. Like read, which returns the original string.
3 Calling read again returns an empty string. This is how real file objects work too; once you read the entire file, you can't read any more without explicitly seeking to the beginning of the file. The StringIO object works the same way.
6 At any time, read will return the rest of the string that you haven't read yet. All of this is exactly how file objects work; hence the term file-like object.

Example 10.5. Parsing XML from a string (the file-like object way)

>>> ssock = StringIO.StringIO(contents)
>>> xmldoc = minidom.parse(ssock) 1
>>> ssock.close()
>>> print xmldoc.toxml()
<?xml version="1.0" ?>
1 Now you can pass the file-like object (really a StringIO) to minidom.parse, which will call the object's read method and happily parse away, never knowing that its input came from a hard-coded string.

So now you know how to use a single function, minidom.parse, to parse an XML document stored on a web page, in a local file, or in a hard-coded string. For a web page, you use urlopen to get a file-like object; for a local file, you use open; and for a string, you use StringIO. Now let's take it one step further and generalize these differences as well.

Example 10.6. openAnything

def openAnything(source):                  1
    import urllib                         
        return urllib.urlopen(source)      2
    except (IOError, OSError):            

        return open(source)                3
    except (IOError, OSError):            

    # treat source as string
    import StringIO                       
    return StringIO.StringIO(str(source))  4
1 The openAnything function takes a single parameter, source, and returns a file-like object. source is a string of some sort; it can either be a URL (like ''), a full or partial pathname to a local file (like 'binary.xml'), or a string that contains actual XML data to be parsed.
2 First, you see if source is a URL. You do this through brute force: you try to open it as a URL and silently ignore errors caused by trying to open something which is not a URL. This is actually elegant in the sense that, if urllib ever supports new types of URLs in the future, you will also support them without recoding. If urllib is able to open source, then the return kicks you out of the function immediately and the following try statements never execute.
3 On the other hand, if urllib yelled at you and told you that source wasn't a valid URL, you assume it's a path to a file on disk and try to open it. Again, you don't do anything fancy to check whether source is a valid filename or not (the rules for valid filenames vary wildly between different platforms anyway, so you'd probably get them wrong anyway). Instead, you just blindly open the file, and silently trap any errors.
4 By this point, you need to assume that source is a string that has hard-coded data in it (since nothing else worked), so you use StringIO to create a file-like object out of it and return that. (In fact, since you're using the str function, source doesn't even need to be a string; it could be any object, and you'll use its string representation, as defined by its __str__ special method.)


Example 10.7. Using openAnything in

class KantGenerator:
    def _load(self, source):
        sock = toolbox.openAnything(source)
        xmldoc = minidom.parse(sock).documentElement
        return xmldoc