1. 程式人生 > >《Python核心程式設計》第9章 檔案和輸入輸出 練習

《Python核心程式設計》第9章 檔案和輸入輸出 練習

9–1. 檔案過濾.

顯示一個檔案的所有行, 忽略以井號( # )開頭的行. 這個字元被用做Python , Perl, Tcl, 等大多指令碼檔案的註釋符號.附加題: 處理不是第一個字元開頭的註釋.

try:
    f = open('test.txt')
    for item in f:
        if item.startswith('#'):
            continue
        elif '#' in item:
            index = item.rfind('#')
            print item[0:index]
        else:
            print item,
except IOError, e:
    print e
else:
    f.close()
9–2. 檔案訪問.

提示輸入數字 N 和檔案 F, 然後顯示檔案 F 的前 N 行.

name = raw_input('file name: ')
n = int(raw_input('lines: '))
with open(name) as f:
    for i, item in enumerate(f):
        print item,
        if i + 1 == n:
            break
9–3. 檔案資訊.

提示輸入一個檔名, 然後顯示這個文字檔案的總行數.

name = raw_input('file name: ')
with open(name) as f:
    print len(f.readlines()) 
9–4. 檔案訪問.

寫一個逐頁顯示文字檔案的程式. 提示輸入一個檔名, 每次顯示文字檔案的 25 行, 暫停並向用戶提示"按任意鍵繼續.", 按鍵後繼續執行.

name = raw_input('file name: ')
page = 25
with open(name) as f:
    for i, item in enumerate(f):
        if (i + 1) % page == 0:
            raw_input('Press any key to continue...')
        print item,

9–6. 檔案比較.

寫一個比較兩個文字檔案的程式. 如果不同, 給出第一個不同處的行號和列號.

def diff(str1, str2):
    for i in xrange(len(str1)):
        if (i < len(str2)) and (str1[i] != str2[i]) or (i == len(str2)):
            return i
            

def filecmp(file1, file2):
    from os.path import getsize
    if getsize(file1.name) == 0 or getsize(file2.name) == 0:
        return (1, 1)
    for i, item in enumerate(file1):
        try:
            string = file2.next()
            if item != string:
                col = diff(item, string) + 1
                row = i + 1
                return (row, col)
        except StopIteration:
            break

if __name__ == '__main__':
    f1 = open(raw_input('the first file: '))
    f2 = open(raw_input('the second file: '))
    result = filecmp(f1, f2)
    if not result:
        print 'the same'
    else:
        print result

    f1.close()
    f2.close()
9–7. 解析檔案.

Win32 使用者: 建立一個用來解析 Windows .ini 檔案的程式. POSIX 使用者:建立一個解析 /etc/serves 檔案的程式. 其它平臺使用者: 寫一個解析特定結構的系統配置檔案的程式.

section = {}
with open(r'c:\windows\win.ini') as f:
    for item in f:
        if item.startswith(';'):
            continue
        if item.startswith('['):
            data = {}
            name = item[1:item.rfind(']')]
            section[name] = data
            continue
        if item.count('=') > 0 and data != None:
            index = item.find('=')
            key = item[0:index]
            value = item[index+1:].strip()
            data[key] = value
print section
9–8. 模組研究.

提取模組的屬性資料. 提示使用者輸入一個模組名(或者從命令列接受輸入).然後使用 dir() 和其它內建函式提取模組的屬性, 顯示它們的名字, 型別, 值.

name = raw_input('module name: ')
obj = __import__(name)
ls = dir(obj)
for item in ls:
    print 'name: ', item
    print 'type: ', type(getattr(obj, item))
    print 'value: ', getattr(obj, item)
    print
9–9. Python 文件字串.

進入 Python 標準庫所在的目錄. 檢查每個 .py 檔案看是否有__doc__ 字串, 如果有, 對其格式進行適當的整理歸類. 你的程式執行完畢後, 應該會生成一個漂亮的清單. 裡邊列出哪些模組有文件字串, 以及文件字串的內容. 清單最後附上那些沒有文件字串模組的名字.附加題: 提取標準庫中各模組內全部類(class)和函式的文件.

#get a list of modules
from os import chdir, listdir
path = r'c:\python27\Lib'
chdir(path)
ls = [item for item in listdir(path) if item.endswith('.py')]
db = {}.fromkeys(ls)

#scan __doc__ in modules
for item in ls:
    with open(item) as f:
        doc = ''
        start = False
        for line in f:
            if line.strip().startswith('"""') and not start:
                start = True
                doc += line
                if doc.strip().endswith('"""') and len(doc.strip()) > 3:
                    start = False
                    break
            elif line.strip().endswith('"""'):
                start = False
                doc += line
                break
            elif start:
                doc += line
        db[item] = doc
empty = []
full = []
for key in sorted(db):
    if db[key] == '':
        empty.append(key)
    else:
        full.append(key)

print 'moudles without doc:'
for item in empty:
    print item,

print
print 'moudles with doc:'
for item in full:
    print item,

print
print 'values:'
for key in full:
    print 'module: ', key
    print 'doc: ', db[key]
    print
9–11. Web 站點地址.
a) 編寫一個 URL 書籤管理程式. 使用基於文字的選單, 使用者可以新增, 修改或者刪除書籤資料項. 書籤資料項中包含站點的名稱, URL 地址, 以及一行簡單說明(可選). 另外提供檢索功能,可以根據檢索關鍵字在站點名稱和 URL 兩部分查詢可能的匹配. 程式退出時把資料儲存到一個磁碟檔案中去; 再次執行時候載入儲存的資料.
b)改進 a) 的解決方案, 把書籤輸出到一個合法且語法正確的 HTML 檔案(.html 或 htm )中,這樣使用者就可以使用瀏覽器檢視自己的書籤清單. 另外提供建立"資料夾"功能, 對相關的書籤進行分組管理.
附加題: 請閱讀 Python 的 re 模組瞭解有關正則表示式的資料, 使用正則表示式對使用者輸入的 URL 進行驗證.
import shelve

def append_bookmark(ls, name, url, remark, group='default'):
    assert isinstance(ls, list)
    ls.append((name, url, remark, group))

def edit_bookmark(ls, index, name, url, remark, group='default'):
    assert isinstance(ls, list)
    ls[index] = (name, url, remark, group)

def delete_bookmark(ls, index):
    assert isinstance(ls, list)
    ls.pop(index)

def find_bookmark(ls, fname, furl):
    assert isinstance(ls, list)
    for i, item in enumerate(ls):
        (name, url, remark, _) = item
        if fname and furl :
            if (furl in url) and (fname in name):
                return i
        if fname and (fname in name):
            return i
        if furl and (furl in url):
            return i
    else:
        return -1
def output2html(ls, filename):
    assert isinstance(ls, list)
    fmt = '%d\t%s\t<a href=%s>%s</a>\t%s\t%s<br>'
    with open(filename, 'w') as f:
        f.write('<html><head><title>bookmark</title></head><body>')
        for i, item in enumerate(ls):
            (name, url, remark, group) = item
            content = fmt % (i+1, name, r'http:\\' + url, url, remark, group) 
            f.write(content)
        f.write('</body></html>')

def sort_list(ls):
    ls.sort(lambda x, y: cmp(x[3], y[3]))

def save(ls, filename):
    try:
        db = shelve.open(filename, 'c')
        db['data'] = ls
    finally:
        db.close()

def load(filename):
    db = shelve.open(filename, 'r')
    return db['data']

def print_all(ls):
    for i, item in enumerate(ls):
        print '%d\t%s\t%s\t%s\t%s' % (i+1, item[0], item[1], item[2], item[3])

def get_input():
    name = raw_input('name; ')
    url = raw_input('url: ')
   remark = raw_input('remark: ')
    group = raw_input('group: ')
    return (name, url, remark, group)

def show_menu():
    try:
        ls = load('sdata.txt')
    except:
        ls = []

    while True:
        print '(A)ppend'
        print '(E)dit'
        print '(D)elete'
        print '(F)ind'
        print '(S)how'
        print '(O)utput to html'
        print '(Q)uit'
        ch = raw_input('input your choice: ')[0].lower()
        if ch not in 'aedfsbo':
            save(ls, 'sdata.txt')
            break
        if ch == 'a':
            data = get_input()
            append_bookmark(ls, *data)
        elif ch == 'e':
            index = int(raw_input('index: '))
            data = get_input()
            edit_bookmark(ls, index, *data)
        elif ch == 'd':
            index = int(raw_input('index: '))
            delete_bookmark(ls, index)
        elif ch == 'f':
            name = raw_input('name: ')
            url = raw_input('url: ')
            index = find_bookmark(ls, name, url)
            if index == -1:
                print 'not found'
            else:
                print ls[index]
        elif ch == 's':
            sort_list(ls)
            print_all(ls)
        elif ch == 'o':
            sort_list(ls)
            output2html(ls, 'bookmark.html')

if __name__ == '__main__':
    show_menu()
9–13. 命令列引數
a) 什麼是命令列引數, 它們有什麼用?
b) 寫一個程式, 打印出所有的命令列引數.
from sys import argv
for item in argv:
    print item
9–15. 複製檔案.

提示輸入兩個檔名(或者使用命令列引數). 把第一個檔案的內容複製到第二個檔案中去.

def copyfile(src, dst):
    with open(dst, 'a') as f:
        with open(src) as s:
            for item in s:
                f.write(item)

if __name__ == '__main__':
    src = raw_input('copy from: ')
    dst = raw_input('copy to: ')
    copyfile(src, dst)

9–16. 文字處理.

人們輸入的文字常常超過螢幕的最大寬度. 編寫一個程式, 在一個文字檔案中查詢長度大於 80 個字元的文字行. 從最接近 80 個字元的單詞斷行, 把剩餘檔案插入到
下一行處.程式執行完畢後, 應該沒有超過 80 個字元的文字行了.

from os import linesep

def nearest(words, maxlen):
    index = -1
    if len(words) > maxlen:
        ch = words[maxlen-1]
        if ch.isalpha() and not words[maxlen].isspace():
            index = words.rfind(' ', 0, maxlen-1)
    return index

def wrap(filename, maxlen=80):
    with open(filename) as f:
        ls = [item for item in f]
    print [len(item) for item in ls]
    for i, item in enumerate(ls):
        if len(item) > maxlen:
            index = nearest(item, maxlen)
            if index == -1:
                tail = item[maxlen:]
                ls[i] = item[:maxlen]
            else:
                tail = item[index:]
                ls[i] = item[:index]
            if (i + 1 < len(ls)):
                ls[i+1] = tail.strip() + ls[i+1]
            else:
                ls.append(tail)
                
    string = linesep.join(ls)
    print [len(item) for item in ls]
    with open(filename, 'w') as f:
        f.write(string)
    
            
if __name__ == '__main__':
    filename = raw_input('file name: ')
    wrap(filename)
9–17. 文字處理.

建立一個原始的文字檔案編輯器. 你的程式應該是選單驅動的, 有如下這些選項:
1) 建立檔案(提示輸入檔名和任意行的文字輸入),
2) 顯示檔案(把檔案的內容顯示到螢幕),
3) 編輯檔案(提示輸入要修改的行, 然後讓使用者進行修改),
4) 儲存檔案, 以及
5) 退出.

def create(filename, content):
    with open(filename, 'w') as f:
        f.write(content)

def show(filename):
    with open(filename) as f:
        for i, item in enumerate(f):
            print i, item,

def edit(filename, index, content):
    assert filename != ''
    with open(filename) as f:
        ls = [item for item in f]
    ls[index] = content
    return ls

def save(filename, ls):
    content = ''.join(ls)
    with open(filename, 'w') as f:
        f.write(content)


def showmenu():
    filename = ''
    ls = []
    while True:
        print '\n1.Create'
        print '2.Show'
        print '3.Edit'
        print '4.Save'
        print '5.quit'
        ch = raw_input('input your choice: ')[0]

        if ch not in '1234':
            break
        if ch == '1':
            filename = raw_input('file name: ')
            content = raw_input('content of file: ')
            create(filename, content)
        elif ch == '2':
            filename = raw_input('file name: ')
            show(filename)
        elif ch == '3':
            if filename == '':
                filename = raw_input('file name: ')
            index = int(raw_input('index: '))
            content = raw_input('content: ')
            ls = edit(filename, index, content)
        elif ch == '4':
            if len(ls) > 0:
                save(filename, ls)
            print 'saved succeed'

if __name__ == '__main__':
    showmenu()
9–18. 搜尋檔案.

提示輸入一個位元組值(0 - 255)和一個檔名. 顯示該字元在檔案中出現的次數.

def counts(filename, value):
    ch = chr(value)
    with open(filename, 'rb') as f:
        total = sum(item.count(ch) for item in f)
    return total

if __name__ == '__main__':
    filename = raw_input('file name: ')
    value = int(raw_input('value: '))
    print counts(filename, value)
9–19. 建立檔案.

建立前一個問題的輔助程式. 建立一個隨機位元組的二進位制資料檔案, 但某一特定位元組會在檔案中出現指定的次數. 該程式接受三個引數:
1) 一個位元組值( 0 - 255 ),
2) 該字元在資料檔案中出現的次數, 以及
3) 資料檔案的總位元組長度.
你的工作就是生成這個檔案, 把給定的位元組隨機散佈在檔案裡, 並且要求保證給定字元在檔案中只出現指定的次數, 檔案應精確地達到要求的長度.

from random import randint
def create(filename, value, total, maxlen):
    assert 0 <= value <= 255
    ls = [chr(randint(0, 255)) for i in xrange(maxlen-total)]
    ch = chr(value)
    for i in xrange(total-ls.count(ch)):
        ls.insert(randint(0, len(ls)-1), ch)
    for i in xrange(maxlen - len(ls)):
        ls.insert(randint(0, len(ls)-1), chr(randint(0, value-1)))
    with open(filename, 'wb') as f:
        f.write(''.join(ls))

if __name__ == '__main__':
    filename = raw_input('file name: ')
    value = int(raw_input('value: '))
    total = int(raw_input('total: '))
    maxlen = int(raw_input('max length of file: '))
    create(filename, value, total, maxlen)
9–20. 壓縮檔案.

寫一小段程式碼, 壓縮/解壓縮 gzip 或 bzip 格式的檔案. 可以使用命令列下的 gzip 或 bzip2 以及 GUI 程式 PowerArchiver , StuffIt , 或 WinZip 來確認你的 Python支援這兩個庫.

import gzip

def compress(zipfile, filename):
    obj = gzip.open(zipfile, 'wb')
    with open(filename, 'rb') as f:
        obj.writelines(f)
    obj.close()

def decompress(zipfile, filename):
    obj = gzip.open(zipfile, 'rb')
    content = obj.read()
    with open(filename, 'wb') as f:
        f.write(content)

if __name__ == '__main__':
    compress('test.gzip', 'wx.txt')
    decompress('test.gzip', 'dc.txt')
9–21. ZIP 歸檔檔案.

建立一個程式, 可以往 ZIP 歸檔檔案加入檔案, 或從中提取檔案,有可能的話, 加入建立ZIP 歸檔檔案的功能.

import zipfile

def add_file(zipname, filename):
    with zipfile.ZipFile(zipname, 'a') as obj:
        obj.write(filename)

def read_file(zipname, filename):
    with zipfile.ZipFile(zipname, 'r') as obj:
        content = obj.read(filename)
        with open(filename, 'w') as f:
            f.write(content)

if __name__ == '__main__':
    add_file('answer1.zip', 'wx.txt')
    read_file('answer1.zip', 'answer1.txt')

9–22. ZIP 歸檔檔案.

unzip -l 命令顯示出的 ZIP 歸檔檔案很無趣. 建立一個 Python指令碼 lszip.py , 使它可以顯示額外資訊: 壓縮檔案大小, 每個檔案的壓縮比率(通過比較壓縮前後文件大小), 以及完成的 time.ctime() 時間戳, 而不是隻有日期和 HH:MM .
提示: 歸檔檔案的 date_time 屬性並不完整, 無法提供給 time.mktime() 使用....這由你自己決定.

import zipfile
import os
import time

filename = raw_input('zip file name: ')
print 'zip file size: ', os.stat(filename).st_size
f = zipfile.ZipFile(filename, 'r')
print 'filename\tdatetime\tsize\tcompress size\trate'
for info in f.infolist():
    t = time.ctime(time.mktime(tuple(list(info.date_time) + [0, 0, 0])))
    print '%s\t%s\t%d\t%d\t%.2f%%' % (info.filename, t, info.file_size, info.compress_size, float(info.compress_size) / info.file_size * 100)
f.close()
9–23. TAR 歸檔檔案.

為 TAR 歸檔檔案建立類似上個問題的程式. 這兩種檔案的不同之處在於 ZIP 檔案通常是壓縮的, 而 TAR 檔案不是, 只是在 gzip 和 bzip2 的支援下才能完成壓縮工作. 加入任意一種壓縮格式支援.
附加題: 同時支援 gzip 和 bzip2 .

import tarfile
import time

filename = raw_input('file name: ')
if not tarfile.is_tarfile(filename):
    print "it's not a tarfile"
else:
    tar = tarfile.open(filename, 'r')
    for info in tar:
        print info.name, info.size, time.ctime(info.mtime)
    tar.close()
9–24. 歸檔檔案轉換.

參考前兩個問題的解決方案, 寫一個程式, 在 ZIP (.zip) 和TAR/gzip (.tgz/.tar.gz) 或 TAR/bzip2 (.tbz/.tar.bz2) 歸檔檔案間移動檔案. 檔案可能是已經存在的, 必要時請建立檔案.

import tarfile
import zipfile
import os

def movefile(src, dst, filename):
    if src.endswith('.zip') and dst.endswith(('.tar.gz', '.tgz', '.tbz', '.tar.bz2')):
        zipobj = zipfile.ZipFile(src, 'r')
        content = zipobj.read(filename)
        zipobj.close()
        
        with open(filename, 'w') as f:
            f.write(content)

        tar = tarfile.open(dst, 'r')
        ls = tar.getnames()
        tar.extractall()
        tar.close()
        
        mode = 'w:gz' if dst.endswith(('tar.gz', '.tgz')) else 'w:bz2'
        tar = tarfile.open(dst, mode)
        for name in ls+[filename]:
            tar.add(name)
            os.remove(name)
        tar.close()
    elif src.endswith(('.tar.gz', '.tgz', '.tbz', '.tar.bz2')) and dst.endswith('.zip'):
        tar = tarfile.open(src, 'r')
        tar.extract(filename)
        tar.close()

        zipobj = zipfile.ZipFile(dst, 'a')
        zipobj.write(filename)
        zipobj.close()
        os.remove(filename)

if __name__ == '__main__':
    os.chdir(r'.\wx')
    movefile('Desktop.zip', 'sample.tar.bz2', 'answer.txt')
    movefile('sample.tar.gz', 'Desktop.zip', 'wx.txt')
9–25. 通用解壓程式.

建立一個程式, 接受任意數目的歸檔檔案以及一個目標目錄做為引數.歸檔檔案格式可以是 .zip, .tgz, .tar.gz, .gz, .bz2, .tar.bz2, .tbz 中的一種或幾種. 程式會把第一個歸檔檔案解壓後放入目標目錄, 把其它歸檔檔案解壓後放入以對應檔名命名的目錄下(不包括副檔名). 例如輸入的檔名為 header.txt.gz 和 data.tgz , 目錄為 incoming ,header.txt 會被解壓到 incoming 而 data.tgz 中的檔案會被放入 incoming/data .

import tarfile
import zipfile
import os

def extract(path, filename):
    if filename.endswith('.zip'):
        with zipfile.ZipFile(filename, 'r') as f:
            f.extractall(path)
    elif filename.endswith(('.tgz', '.tar.gz', '.bz2', '.tbz', 'tar')):
        with tarfile.open(filename, 'r') as f:
            f.extractall(path)

def decompress(target, *files):
    if not os.path.exists(target):
        os.mkdir(target)
    extract(target, files[0])
    for name in files[1:]:
        dirname = os.path.splitext(os.path.basename(name))[0]
        dirname = '.\\' + target + '\\' + dirname
        os.mkdir(dirname)
        extract(dirname, name)

if __name__ == '__main__':
    os.chdir(r'.\wx')
    decompress('test', 'sample.tar', 'Desktop.zip', 'sample1.tar.bz2', 'sample2.tar.gz')