《Python核心程式設計》第9章 檔案和輸入輸出 練習
9–1. 檔案過濾.
顯示一個檔案的所有行, 忽略以井號( # )開頭的行. 這個字元被用做Python , Perl, Tcl, 等大多指令碼檔案的註釋符號.附加題: 處理不是第一個字元開頭的註釋.
9–2. 檔案訪問.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()
提示輸入數字 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. 檔案資訊. 提示輸入一個檔名, 然後顯示這個文字檔案的總行數.
9–4. 檔案訪問.name = raw_input('file name: ') with open(name) as f: print len(f.readlines())
寫一個逐頁顯示文字檔案的程式. 提示輸入一個檔名, 每次顯示文字檔案的 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')