Part V, Modules

See Test Your Knowledge: Part V Exercises in Chapter 24 for the exercises.

 

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

 
  1. Import basics. When you’re done, your file (mymod.py) and interaction should look similar to the following; remember that Python can read a whole file into a list of line strings, and the len built-in returns the lengths of strings and lists:

    def countLines(name):
        file = open(name)
        return len(file.readlines())

    def countChars(name):
        return len(open(name).read())

    def test(name):                                  # Or pass file object
        return countLines(name), countChars(name)    # Or return a dictionary

    % python
    >>> import mymod
    >>> mymod.test('mymod.py')
    (10, 291)

    Note that these functions load the entire file in memory all at once, so they won’t work for pathologically large files too big for your machine’s memory. To be more robust, you could read line by line with iterators instead and count as you go:

    def countLines(name):
        tot = 0
        for line in open(name): tot += 1
        return tot

    def countChars(name):
        tot = 0
        for line in open(name): tot += len(line)
        return tot

    A generator expression can have the same effect: sum(len(line) for line in open(name)). On Unix, you can verify your output with a wc command; on Windows, right-click on your file to view its properties. Note that your script may report fewer characters than Windows does—for portability, Python converts Windows \r\n line-end markers to \n, thereby dropping one byte (character) per line. To match byte counts with Windows exactly, you must open in binary mode ('rb'), or add the number of bytes corresponding to the number of lines.
    The “ambitious” part of this exercise (passing in a file object so you only open the file once), will require you to use the seek method of the built-in file object. It works like C’s fseek call (and calls it behind the scenes): seek resets the current position in the file to a passed-in offset. After a seek, future input/output operations are relative to the new position. To rewind to the start of a file without closing and reopening it, call file.seek(0); the file read methods all pick up at the current position in the file, so you need to rewind to reread. Here’s what this tweak would look like:

    def countLines(file):
        file.seek(0)                                 # Rewind to start of file
        return len(file.readlines())

    def countChars(file):
        file.seek(0)                                 # Ditto (rewind if needed)
        return len(file.read())

    def test(name):
        file = open(name)                            # Pass file object
        return countLines(file), countChars(file)    # Open file only once

    >>> import mymod2
    >>> mymod2.test("mymod2.py")
    (11, 392)

  2. from/from *. Here’s the from * part; replace * with countChars to do the rest:

    % python
    >>> from mymod import *
    >>> countChars("mymod.py")
    291

  3. __main__. If you code it properly, it works in either mode (program run or module import):

    def countLines(name):
        file = open(name)
        return len(file.readlines())

    def countChars(name):
        return len(open(name).read())

    def test(name):                                  # Or pass file object
        return countLines(name), countChars(name)    # Or return a dictionary

    if __name__ == '__main__':
        print(test('mymod.py'))

    % python mymod.py
    (13, 346)

    This is where I would probably begin to consider using command-line arguments or user input to provide the filename to be counted, instead of hardcoding it in the script (see Chapter 24 for more on sys.argv, and Chapter 10 for more on input):

    if __name__ == '__main__':
        print(test(input('Enter file name:'))

    if __name__ == '__main__':
        import sys
        print(test(sys.argv[1]))

  4. Nested imports. Here is my solution (file myclient.py):

    from mymod import countLines, countChars
    print(countLines('mymod.py'), countChars('mymod.py'))

    % python myclient.py
    13 346

    As for the rest of this one, mymod’s functions are accessible (that is, importable) from the top level of myclient, since from simply assigns to names in the importer (it works as if mymod’s defs appeared in myclient). For example, another file can say:

    import myclient
    myclient.countLines(...)

    from myclient import countChars
    countChars(...)

    If myclient used import instead of from, you’d need to use a path to get to the functions in mymod through myclient:

    import myclient
    myclient.mymod.countLines(...)

    from myclient import mymod
    mymod.countChars(...)

    In general, you can define collector modules that import all the names from other modules so they’re available in a single convenience module. Using the following code, you get three different copies of the name somename (mod1.somename, collector.somename, and __main__.somename); all three share the same integer object initially, and only the name somename exists at the interactive prompt as is:

    # File mod1.py
    somename = 42

    # File collector.py
    from mod1 import *                               # Collect lots of names here
    from mod2 import *                               # from assigns to my names
    from mod3 import *

    >>> from collector import somename

  5. Package imports. For this, I put the mymod.py solution file listed for exercise 3 into a directory package. The following is what I did to set up the directory and its required __init__.py file in a Windows console interface; you’ll need to interpolate for other platforms (e.g., use mv and vi instead of move and edit). This works in any directory (I just happened to run my commands in Python’s install directory), and you can do some of this from a file explorer GUI, too.
    When I was done, I had a mypkg subdirectory that contained the files __init__.py and mymod.py. You need an __init__.py in the mypkg directory, but not in its parent; mypkg is located in the home directory component of the module search path. Notice how a print statement coded in the directory’s initialization file fires only the first time it is imported, not the second:

    C:\python30> mkdir mypkg
    C:\Python30> move mymod.py mypkg\mymod.py
    C:\Python30> edit mypkg\__init__.py
    ...coded a print statement...
    C:\Python30> python
    >>> import mypkg.mymod
    initializing mypkg
    >>> mypkg.mymod.countLines('mypkg\mymod.py')
    13
    >>> from mypkg.mymod import countChars
    >>> countChars('mypkg\mymod.py')
    346

  6. Reloads. This exercise just asks you to experiment with changing the changer.py example in the book, so there’s nothing to show here.
  7. Circular imports. The short story is that importing recur2 first works because the recursive import then happens at the import in recur1, not at a from in recur2.
    The long story goes like this: importing recur2 first works because the recursive import from recur1 to recur2 fetches recur2 as a whole, instead of getting specific names. recur2 is incomplete when it’s imported from recur1, but because it uses import instead of from, you’re safe: Python finds and returns the already created recur2 module object and continues to run the rest of recur1 without a glitch. When the recur2 import resumes, the second from finds the name Y in recur1 (it’s been run completely), so no error is reported. Running a file as a script is not the same as importing it as a module; these cases are the same as running the first import or from in the script interactively. For instance, running recur1 as a script is the same as importing recur2 interactively, as recur2 is the first module imported in recur1.