Exceptions and File Handling¶
Goal:
- Catching exceptions with
try...except - Protecting external resources with
try...finally - Reading from files
- Assigning multiple values at once in a
forloop - Using the
osmodule for all your cross-patform file manipulation needs - Dynamically instantiating classes of unknown type by treating classes as objects and passing them around
6.1. Handling Exceptions¶
Python uses try...except to handle exceptions and raise to generate them.
Java uses try...catch to handle exceptions, and throw to generate them.
Example 6.1. Opening a Non-Existent File
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | In [1]: fsock = open("/notthere","r")
---------------------------------------------------------------------------
IOError Traceback (most recent call last)
<ipython-input-1-12d3ba34a1ce> in <module>()
----> 1 fsock = open("/notthere","r")
IOError: [Errno 2] No such file or directory: '/notthere'
In [2]: try:
...: fsock = open("/notthere")
...: except IOError:
...: print "The file does not exist, exiting gracefully"
...: print "This line will always print"
...:
The file does not exist, exiting gracefully
This line will always print
|
- [2]:
- When the
openmethod raises anIOErrorexception, you’re ready for it. Theexcept IOError:line catches the exception and executes your own block of code, which in this case just prints a more pleasant error message.
- Once an exception has been handled, processing continues normally on the first line after the
try...exceptblock. Note that this line will always print, whether or not an exception occurs. If you really did have a file callednottherein your root directiory, the call toopenwould succeed, theexceptclause would be ignored, and this line would still be executed.
- When the
6.1.1 Using Exceptions For other Purposes¶
a. Check whether the imported module work¶
A common use in the standard Python library is to try to import a module, and then check whether it worked. (You can use this to define multiple levels of functionality based on which modules are available at run-time, or to support multiple platforms)
Example 6.2 Supporting Platform-Specific Functionality
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | """
this example shows how to use an exception to support platform-specific functionality
this code comes from the getpass module, a wrapper module for getting a password from user
"""
# Bind the name getpass to the appropriate function
try:
import termios, TERMIOS
except ImportError:
try:
import msvcrt
except ImportError:
try:
from EasyDialogs import AskPassword
except ImportError:
getpass = default_getpass
else:
getpass = AskPassword
else:
getpass = win_getpass
else:
getpass = unix_getpass
|
- [8]:
termiosis a UNIX-specific module that provides low-level control over the input terminal. If this module is not available (because it’s not on your system, or your system doesn’t support it), the import fails and Python raises anImportError, which you catch.- [11]:
msvcrtis a Windows-specific module that provides an API to many useful functions in the Microsoft Visual C++ runtime services.- [14]:
- if the first two didn’t work, you try to import a function from
EasyDialogs, which is a Mac OS-specific module that provides functions to pop up dialog boxes of various types. - [16]:
- None of these platform-specific modules is available, so you need to fall back on a default password input
function (which is defined elsewhere in the
getpassmodule). Notice what you’re doing here: assigning the functiondefault_getpassto the variablegetpass. If you read the officialgetpassdocumentation, it tells you that thegetpassmodule defines agetpassfunction. It does this by bindinggetpassto the correct function for your platform. Then when you call thegetpassfunction, you’re really calling a platform-specific function that this code has set up for you. You don’t need to know which platform your code is running on – just callgetpass, and it will always do the right thing. - [17]:
- A
try...exceptblock can have anelseclause. If no exception is raised during thetryblock, theelseclause is executed afterwards. In this case, that means that thefrom EasyDialogs import AskPasswordimport worked, so you should bindgetpassto theAskPasswordfunction.
6.2. Working with File Object¶
6.2.1 Opening files¶
Key Func
open(file)
Example 6.3 Opening a File
In [1]: f = open("./Music/Taylor Swift - State of Grace.mp3","rb")
In [2]: f
Out[2]: <open file './Music/Taylor Swift - State of Grace.mp3', mode 'rb' at 0x9132808>
In [3]: f.mode
Out[3]: 'rb'
In [4]: f.name
Out[4]: './Music/Taylor Swift - State of Grace.mp3'
- [1]:
Python has a built-in function,
open, for opening a file on disk.The
openmethod take up to three parameters: a filename, a mode, and a buffering parameter. Only the first one, filename, is required; the other two are optional. If not specific, the file is opened for reading in text mode.Here you are opening the file for reading in binary mode.
- [2]:
openreturns a file object, which has methods and attributes for getting infomation about and manipulating the opened file.- [3]:
- The
modeattribute of a file object tells you in which mode the file was opened. - [4]:
- The
nameattribute of a file object tells you the name of the file that the file object has open.
6.2.2 Reading Files¶
Key Func
f.tell(), f.seek(...), f.read(...)
Example 6.4. Reading a File
In [5]: f
Out[5]: <open file './Music/Taylor Swift - State of Grace.mp3', mode 'rb' at 0x9132808>
In [6]: f.tell()
Out[6]: 0L
In [7]: f.seek(-128,2)
In [8]: f.tell()
Out[8]: 11934864L
In [9]: tagData = f.read(128)
In [10]: tagData
Out[10]: 'TAGState of Grace\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Taylor Swift\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00State of Grace - Single\x00\x00\x00\x00\x00\x00\x002012\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02'
In [11]: f.tell()
Out[11]: 11934992L
- [6]:
- The
tellmethod of a file object tells you your current position in the open file. Since you haven’t done anything with this file yet, the current position is 0, which is the beginning of the file. - [7]:
The
seekmethod of a file object moves to another position in the open file.The second parameter specifies what the first one means:
0means move to an absolute position (counting from the start of the file)1means move to a relative position (counting from the current position)2means move to a position relative to the end of the file.
Since the MP3 tags you’re looking for are stored at the end of the file, you use
2and tell the file object to move to a position128bytes from the end of the file.
- [9]:
- The
readmethod reads a specified number of bytes from the open file and returns a string with the data that was read. - The optional parameter specifies the maximum number of bytes to read.
If no parameter is specified,
readwill read until the end of the file. - The read data is assigned to the
tagDatavariable, and the current position is updated based on how many bytes were read.
- The
- [11]:
- The
tellmethod confirms that the current position has moved. If you do the math, you’ll see that after reading 128 bytes, the position has been incremented by 128.
6.2.3 Closing Files¶
Open files consume system resourcesm and depending on the file mode, other programs may not be able to access them. It’s important to close files as soon as you’re finished with them.
Example 6.5 Closing a File
In [12]: f
Out[12]: <open file './Music/Taylor Swift - State of Grace.mp3', mode 'rb' at 0x9132808>
In [13]: f.closed
Out[13]: False
In [15]: f.close()
In [16]: f
Out[16]: <closed file './Music/Taylor Swift - State of Grace.mp3', mode 'rb' at 0x9132808>
In [17]: f.closed
Out[17]: True
In [18]: f.seek(0)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-18-d0cb2b3c0dfb> in <module>()
----> 1 f.seek(0)
ValueError: I/O operation on closed file
In [19]: f.tell()
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-19-322ae27c0b94> in <module>()
----> 1 f.tell()
ValueError: I/O operation on closed file
In [20]: f.read()
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-20-bacd0e0f09a3> in <module>()
----> 1 f.read()
ValueError: I/O operation on closed file
In [21]: f.close()
- [13]:
- The
closedattribute of a file object indicates whether the object has a file open or not. In this case, the file is still open (closedisFalse) - [15]:
- To close a file, call the
closemethod of the file object. This frees the lock (if any) that you were holding on the file, flushes buffered writes (if any) that the system hadn’t gotten around to actually wiriting yet, and releases the system resources. - [18]:
- Just because a file is closed doesn’t mean that the file object ceases to exist. The variable
fwill continue to exist until it goes out of scope or gets manually deleted. However, none of the methods that manipulate an open file will work once the file has been closed; they will raise an exception. - [21]:
- Calling
closeon a file object whose file is already closed does not raise an exception.
6.2.4 Handling I/O Errors¶
Example 6.6 File Objects in MP3FileInfo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # this example shows how to safely open and read from a file and gracefully handle errors.
try:
fsock = open(filename, "rb", 0)
try:
fsock.seek(-128,2)
tagdata = fsock.read(128)
finally:
fsock.close()
.
.
.
except IOError:
pass
|
- [3]:
- Because opening and reading files is risky and may raise an exception,
all of this code is wrapped in a
try...exceptblock. - [4]:
- The
openfunction may raise anIOError. (Maybe the file doesn’t exist) - [6]:
- The
seekmethod may raise anIOError. (Maybe the file is smaller than 128 bytes) - [7]:
- The
readmethod may raise anIOError. (Maybe the disk has a bad sector, or it’s on a network drive and the network just went down) - [8]:
try...finallyblock:- code in the
finallyblock will always be executed, even if something in thetryblock raises an exception.
- code in the
6.2.5 Writing to Files¶
There are two basic file modes:
- “Append” (
a) mode will add data to the end of the file.- “write” (
w) mode will overwrite the file.
Either mode will create the file automatically if it doesn’t already exist.
Example 6.7. Writing to Files
In [22]: logfile = open('test.log','w')
In [23]: logfile.write('test succeeded')
In [24]: logfile.close()
In [25]: print file('test.log').read()
test succeeded
In [26]: logfile = open('test.log','a')
In [27]: logfile.write('line 2')
In [28]: logfile.close()
In [29]: print file('test.log').read()
test succeededline 2
- [21]:
- opening the file for writing. (The second parameter
wmeans open the file for writing). The previous contents of that file has gone now. - [23]:
- You can add data to the newly opened file with the
writemethod of the file object returned byopen. - [25]:
fileis a synonym foropen. This one-liner opens the file, reads its contents, and prints them.- [26]:
- open the file and append to it. (The
aparameter means open the file for appending.) Appending will never harm the existing contents of the file. - [29]:
- Note that carriage returns are not included. Since you didn’t write them explicitly to the file either time,
the file doesn’t include them. You can write a carriage return with the
\ncharacter. Since you didn’t do this, everything you wrote to the file ended up smooshed together on the same line.
6.3. Iterating with for Loops¶
Most other languages don’t have a powerful list datatype like Python,
so you end up doing a lot of manual work, specifying a start, end, and step to define a range of integers or character or other iteratable entities.
But in Python, a for loop simply iterates over a lis, the same way list comprehensions work.
Example 6.8 Introducing the for loop
In [1]: li = ['a','b','e']
In [2]: for s in li:
...: print s
...:
a
b
e
In [3]: print "\n".join(li)
a
b
e
- [2]:
- The syntax for a
forloop is similar to list comprehensions.liis a list, andswill take the value of each element in turn, starting from the first element.
Example 6.9 Simple counters
In [5]: for i in range(5):
...: print i
...:
0
1
2
3
4
for loops are not just for simple counters. They can iterate through all kinds of things.
Example 6.10 Iterating Through a Dictionary
In [8]: import os
In [9]: for k,v in os.environ.items():
...: print "%s=%s" % (k,v)
...:
XAUTHORITY=/home/zeyuan_hu/.Xauthority
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
LESSOPEN=| /usr/bin/lesspipe %s
LOGNAME=zeyuan_hu
USER=zeyuan_hu
[...snip...]
In [10]: print "\n".join(["%s=%s" % (k,v) for k,v in os.environ.items()])
XAUTHORITY=/home/zeyuan_hu/.Xauthority
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
LESSOPEN=| /usr/bin/lesspipe %s
LOGNAME=zeyuan_hu
USER=zeyuan_hu
[...snip...]
- [9]:
os.environis a dictionary of the environment variables defined on your system.os.environ.items()returns a list of tuples:[(key1, value1), (key2, value2), ...]
- [10]:
- this version is slightly faster, because there is only one
printstatement instead of many.
Example 6.11 for loop in MP3FileInfo
1 2 3 4 5 6 7 8 9 10 11 12 | tagDataMap = {"title" : ( 3, 33, stripnulls),
"artist" : ( 33, 63, stripnulls),
"album" : ( 63, 93, stripnulls),
"year" : ( 93, 97, stripnulls),
"comment" : ( 97, 126, stripnulls),
"genre" : (127, 128, ord)}
.
.
.
if tagdata[:3] == "TAG":
for tag, (start, end, parseFunc) in self.tagDataMap.items():
self[tag] = parseFunc(tagdata[start:end])
|
- [6]:
tagDataMapis a class attribute that defines the tags you’re looking for in an MP3 file.- [11]:
- The structure of the
forvariables matches the structure of the elements of the list returned byitems. Remember thatitemsreturns a list of tuples of the form(key, value). The first element of the list is("title", (3, 33, <function stripnulls>)), so the first time around the loop, tag getstitle,startgets3,endgets33, andparseFuncgets the functionstripnulls.
6.4. Using sys.modules¶
Modules, like everything else in Python, are objects.
Once imported, you can always get a reference to a module through the global dictionary sys.modules.
Example 6.12 Introducing sys.modules
In [3]: import sys
In [4]: print '\n'.join(sys.modules.keys())
copy_reg
sre_compile
_sre
encodings
site
__builtin__
sys
__main__
encodings.encodings
abc
posixpath
_weakrefset
errno
re
os
- [3]:
- The
sysmodule contains system-level information, such as the version of Python you’re running (sys.versionorsys.version_info) - [4]:
sys.modulesis a dictionary containing all the modules that have ever been imported since Python was started; the key is the module name, the value is the module object.
Example 6.13 Using sys.modules
In [13]: import fileinfo
In [14]: print '\n'.join(sys.modules.keys())
copy_reg
fileinfo
sre_compile
_sre
encodings
site
__builtin__
sys
__main__
encodings.encodings
abc
posixpath
_weakrefset
errno
re
os
In [15]: fileinfo
Out[15]: <module 'fileinfo' from 'fileinfo.pyc'>
In [16]: sys.modules["fileinfo"]
Out[16]: <module 'fileinfo' from 'fileinfo.pyc'>
- [13]:
- As new modules are imported, they are added to
sys.modules. - [16]:
- Given the name (as a string) of any previously-imported module,
you can get a reference to the module itself through the
sys.modulesdictionary.
Example 6.14 The __module__ Class Attribute
In [17]: from fileinfo import MP3FileInfo
In [18]: MP3FileInfo.__module__
Out[18]: 'fileinfo'
In [19]: sys.modules[MP3FileInfo.__module__]
Out[19]: <module 'fileinfo' from 'fileinfo.pyc'>
- [18]:
- Every Python class has a built-in class attribute __module__, which is the name of the module in which the class is defined.
- [19]:
- Combining this with the
sys.modulesdictionary, you can get a reference to the module in which a class is defined.
6.5. Working with Directories¶
Key Func
os.path.join(path, filename),
os.path.expanduser(pathname including "~")
path, filename = os.path.split(pathname)
filename, fileext = os.path.splitext(filename)
os.listdir(pathname)
os.path.isfile(pathname)
os.path.isdir(pathname)
os.getcwd()
os.path.normcase(file or folder)
glob module
6.5.1 Constructing Pathnames¶
Example 6.16 Constructing Pathnames
In [21]: import os
In [22]: os.path.join("../Music/","Taylor Swift - State of Grace.mp3")
Out[22]: '../Music/Taylor Swift - State of Grace.mp3'
In [23]: os.path.join("../Music","Taylor Swift - State of Grace.mp3")
Out[23]: '../Music/Taylor Swift - State of Grace.mp3'
In [24]: os.path.expanduser("~")
Out[24]: '/home/zeyuan_hu'
In [25]: os.path.join(os.path.expanduser("~"),"Python")
Out[25]: '/home/zeyuan_hu/Python'
- [22]:
os.pathis a reference to a module – which module depends on your platform. Just asgetpassencapsulates differences between platforms by settinggetpassto a platform-specific function,osencapsulates differences between platforms by settingpathto a platform-specific module.- [23]:
joinwill add an extra backslash to the pathname before joining it to the filename.
# constructs a pathname out of one or more partial pathnames.
os.path.join(path, filename)
# expand a pathname that uses ~ to represent the current user's home directory
os.path.expanduser(path including "~")
6.5.2 Splitting Pathnames¶
Example 6.17 Splitting Pathnames
In [26]: os.path.split('../Music/Taylor Swift - State of Grace.mp3')
Out[26]: ('../Music', 'Taylor Swift - State of Grace.mp3')
In [27]: filepath, filename = os.path.split('../Music/Taylor Swift - State of Grace.mp3')
In [28]: filepath
Out[28]: '../Music'
In [29]: filename
Out[29]: 'Taylor Swift - State of Grace.mp3'
In [30]: shortname, extension = os.path.splitext(filename)
In [31]: shortname
Out[31]: 'Taylor Swift - State of Grace'
In [32]: extension
Out[32]: '.mp3'
# splits a full pathname and returns a tuple containing the path and filename.
# you can assign the return value of split function into a tuple of two variables.
path, filename = os.path.split(pathname)
# splits a filename and returns a tuple containing the filename and the file extension
filename, fileext = os.path.splitext(filename)
6.5.3 Listing Directories¶
Example 6.18 Listing Directories
In [35]: os.path.expanduser("~")
Out[35]: '/home/zeyuan_hu'
In [36]: dirname = os.path.expanduser("~")
In [37]: os.listdir(dirname)
Out[37]:
['.pip','.adobe','.gconf','.gnome2','.xscreensaver','Dropbox',
'Music','.profile','py','Videos','.pki','.dropbox','.swp',
'.cache'.'.Xauthority','Documents','.vim', ... ]
In [38]: [f for f in os.listdir(dirname) if os.path.isfile(os.path.join(dirname,f))]
Out[38]:
['.dmrc', '.swp','.Xauthority','.pam_environment','.bash_history',
'.gksu.lock','.install4j','.viminfo','.pulse-cookie','examples.desktop',
'.vimrc~','.bashrc','.vimrc', ...]
In [39]: [f for f in os.listdir(dirname) if os.path.isdir(os.path.join(dirname,f))]
Out[39]:
['Downloads','.matplotlib','.pip','.gnome2','.java','Dropbox','Music',
'py','Videos','.dropbox','.cache','Documents','.vim','Desktop',
'Public','Ubuntu One','Templates','Pictures','.ssh', ... ]
# takes a pathname and returns a list of the contents of the directory
# note that listdir returns both files and folders, with no indication of which is which.
os.listdir(pathname)
# used to separate the files from the folders
# takes a pathname and returns 1 if the path represents a file, and 0 otherwise.
# also works with a partial path, relative to the current working directory.
os.path.isfile(pathname)
# get the current working directory
os.getcwd()
# used to get a list of the subdirectories within a directory
# takes a pathname and returns 1 if the path represents a directory, and 0 otherwise.
os.path.isdir(pathname)
Example 6.19 Listing Directories in fileinfo.py
1 2 3 4 5 | def listDirectory(directory, fileExtList):
"get list of file info objects for files of particular extensions"
fileList = [os.path.normcase(f) for f in os.listdir(directory)]
fileList = [os.path.join(directory, f) for f in fileList if os.path.splitext(f)[1] in fileExtList]
|
- [3]:
normcaseis a useful little function that compensates for case-insensitive operation systems that think thatmahadeva.mp3andmahadeva.MP3are the same file. For instance, on Windows and Mac OS,normcasewill convert the entire filename to lowercase; on UNIX-compatible systems, it will return the filename unchanged.- [5]:
- Iterating through the normalized list with
fagain, you useos.path.splitext(f)to split each filename into name and extension. - For each file, you see if the extension is in the list of file extensions you care about (
fileExtList, which was passed to thelistDirectoryfunction).
- Iterating through the normalized list with
# normalize the case according to operating system defaults
os.path.normcase(file or folder)
Note
Whenever possible, you should use the functions in os and os.path for file, directory, and path manipulations.
Those modules are wrappers for platform-specific modules, so function like os.path.split work on UNIX, Windows, Mac OS,
and any other platform supported by Python.
Example 6.20 Listing Directories with glob
In [42]: os.listdir("/home/zeyuan_hu/Music")
Out[42]: ['a_time_long_forgotten_con.mp3', 'hellraiser.mp3','kairo.mp3',
'long_way_home1.mp3', 'sidewinder.mp3','spinning.mp3']
In [43]: import glob
In [44]: glob.glob("/home/zeyuan_hu/Music/*.mp3")
Out[44]: ["/home/zeyuan_hu/Music/a_time_long_forgotten_con.mp3",
"/home/zeyuan_hu/Music/hellraiser.mp3",
"/home/zeyuan_hu/Music/kairo.mp3",
"/home/zeyuan_hu/Music/long_way_home1.mp3",
"/home/zeyuan_hu/Music/sidewinder.mp3",
"/home/zeyuan_hu/Music/spinning.mp3"]
In [45]: glob.glob("/home/zeyuan_hu/Music/s*.mp3")
Out[45]: ["/home/zeyuan_hu/Music/sidewinder.mp3",
"/home/zeyuan_hu/Music/spinning.mp3"]
In [46]: glob.glob("/home/zeyuan_hu/*/*.mp3")
- [44]:
- The
globmodule, takes a wildcard and returns the full path of all files and directories matching the wildcard. Here the wildcard is a directory path plus “*.mp3”, which will match all.mp3files. Note that each element of the returned list already includes the full path of the file. - [45]:
- If you want to find all the files in a specific directory that start with “s” and end with ”.mp3”, you can do that too.
- [46]:
- Now consider this scenario: you have a music directory, with several subdirectories within it,
with
.mp3files within each subdirectory. You can get a list of all of those with a single call toglob, by using two wildcards at once. One wildcard is the “*.mp3” (to match.mp3files), and one wildcard is within the directory path itself, to match any subdirectory within/home/zeyuan_hu/.
6.6. Putting it All Together¶
Example 6.21. listDirectory
1 2 3 4 5 6 7 8 9 10 11 12 | def listDirectory(directory, fileExtList):
"get list of file info objects for files of particular extensions"
fileList = [os.path.normcase(f) for f in os.listdir(directory)]
fileList = [os.path.join(directory, f) for f in fileList if os.path.splitext(f)[1] in fileExtList]
def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):
"get file info class from filename extension"
subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:]
return hasattr(module, subclass) and getattr(module, subclass) or FileInfo
return [getFileInfoClass(f)(f) for f in fileList]
|
- [1]:
listDirectoryis the main attraction of this entire module. It takes a directory(i.e. /home/zeyuan_hu/Music/) and a list of interesting file extensions (i.e.['.mp3']), and it returns a list of class instances that act like dictionaries that contain metadata about each interesting file in that directory.- [5]:
- This line of code gets a list of the full pathnames of all the files in
directorythat have an interesting file extension (as specified byfileExtList). - [7]:
Python supports nest functions:
The nested functiongetFileInfoClasscan be called only from the function in which it is defined,listDirectory.- [7]:
- This is a function with two arguments;
filenameis required, butmoduleis optional and defaults to the module that contains theFileInfoclass. - [9]:
- This line of code gets the extension of the file (
os.path.splitext(filename)[1]), forces it to uppercase(.upper()), slice off the dot ([1:]), and constructs a class name out of it with string formatting. So/home/zeyuan_hu/Music/mahadeva.mp3becomes.mp3becomes.MP3becomesMP3becomesMP3FileInfo. - [10]:
Having constructed the name of the handler class that would handle this file, you check to see if that handler class actually exists in this module. If it does, you return the class, otherwise you return the base class
FileInfo.This is s a very important point: this function returns a class. Not an instance of a class, but the class itself.
- [12]:
- For each file in the “interesting files” list (
fileList), you callgetFileInfoClasswith the filename(f). CallinggetFileInfoClass(f)returns a class; You then create an instance of this class and pass the filename(fagain), to the__init__method. As you saw earlier in this chapter, the__init__method ofFileInfosetsself["name"], which triggers__setitem__, which is overridden in the descendant (MP3FileInfo) to parse the file appropriately to pull out the file’s metadata. You do all that fo each interesting file and return a list of the resulting instances.
Note
Note that listDirectory is completely generic. It doesn’t know ahead of time which types of files it will be getting,
or which classes are defined that could potentially handle those files. It inspects the directory for the files to
process, and then introspects its own module to see what special handler classes (like MP3FileInfo) are defined.
You can extend this program to handle other types of files simply by defining an appropriately-named class:
HTMLFileInfo for HTML files, DOCFileInfo for Word .doc files., and so forth.
listDirectory will handle them all, without modification, by handing off the real work to the appropriate classes and collating the results.
