通常建議用內(nèi)置函數(shù) open() 打開文件,file 用于類型判斷。
>>> with open("test.txt", "w") as f:
... print isinstance(f, file) // 類型判斷
... f.writelines(map(str, range(10)))
True
File Object 實現(xiàn)了上下文協(xié)議,可確保文件被及時關(guān)閉。實際上,文件對象被回收時總是會調(diào)用 close 方法,所以可以寫下面這樣的代碼。
>>> open("test.txt", "r").read()
'0123456789'
如果要把數(shù)據(jù)寫到磁盤上,除調(diào)用 flush() 外,還得用 sync(),以確保數(shù)據(jù)從系統(tǒng)緩沖區(qū)同步到磁盤。close() 總是會調(diào)用這兩個方法。
打開模式:
文件對象還實現(xiàn)了迭代器協(xié)議,可直接循環(huán)獲取其內(nèi)容。
>>> with open("main.py", "r") as f:
... for line in f: print line
...
讀方法總能判斷不同平臺的換行標(biāo)記,但寫方法不會添加任何換行字符,包括 writelines。
>>> with open("test.txt", "w") as f:
... f.write("a")
... f.writelines("bc")
>>> cat test.txt
abc
如必須按不同平臺寫入換行標(biāo)記,可使用 os.linesep。
>>> os.linesep
'\n'
字符串本身就是序列類型,可以直接用 writelines(str)。readline() 會返回包括換行符在內(nèi)的整個行數(shù)據(jù)。通常建議用迭代器或 xreadlines() 代替 readlines(),后者默認(rèn)一次性讀取整個文件。
用 struct 將其他類型構(gòu)建成二進(jìn)制字節(jié)數(shù)組,然后寫入文件即可。
>>> import struct
>>> data = struct.pack("2i2s", 0x1234, 0xFF56, "ab")
>>> open("test.dat", "w").write(data)
>>> !xxd -g 1 test.dat
0000000: 34 12 00 00 56 ff 00 00 61 62 4...V...ab
>>> struct.unpack("2i2s", open("test.dat").read())
(4660, 65366, 'ab')
>>> with open("test.dat") as f: // 結(jié)構(gòu)化讀取
... def xread(fmt):
... n = struct.calcsize(fmt) // 計算長度
... s = f.read(n)
... return struct.unpack(fmt, s)
... print xread("i")
... print xread("i")
... print xread("2s")
(4660,)
(65366,)
('ab',)
對于相同類型的數(shù)據(jù),可考慮用 array,以獲得更好的性能。
>>> import array
>>> datas = array.array("i")
>>> datas.append(0x1234)
>>> datas.append(0xFF56)
>>> datas.tofile(open("test.dat", "w"))
>>> !xxd -g 1 test.dat
0000000: 34 12 00 00 56 ff 00 00 4...V...
>>> d2 = array.array("i")
>>> d2.fromfile(open("test.dat"), 2)
>>> d2
array('i', [4660, 65366])
類似的還有 bytearray,可作 Buffer 用,詳情參見 struct 章節(jié)。
標(biāo)準(zhǔn)庫 codecs 提供了一個包裝版的 open(),可自動完成編碼轉(zhuǎn)換工作。
>>> import sys
>>> reload(sys)
>>> sys.setdefaultencoding("utf-8")
>>> with codecs.open("test.txt", "w", "gbk") as f:
... f.write("中國")
>>> !xxd -g 1 test.txt
0000000: d6 d0 b9 fa ....
>>> "中國".encode("gbk")
'\xd6\xd0\xb9\xfa'
>>> s = codecs.open("test.txt", encoding = "gbk").read()
>>> s
u'\u4e2d\u56fd'
>>> print s
中國
除使用文件對象外,某些時候還可能需要直接操控文件描述符。
>>> import os
>>> fd = os.open("test.txt", os.O_CREAT | os.O_RDWR, 0644) // 注意是八進(jìn)制。
>>> ls -l test.txt
-rw-r--r-- 1 yuhen staff 6 3 25 10:45 test.txt
>>> os.write(fd, "abc")
3
>>> f = os.fdopen(fd, "r+") // 通過描述符創(chuàng)建文件對象。
>>> f.seek(0, os.SEEK_SET) // 注意調(diào)整位置。
>>> f.read()
'abc'
>>> f.write("123")
>>> f.flush() // os 庫提供的函數(shù)是系統(tǒng)調(diào)用,因此需要把數(shù)據(jù)從用戶緩存
// 刷新到系統(tǒng)緩存。
>>> os.lseek(fd, 0, os.SEEK_SET)
0
>>> os.read(fd, 100)
'abc123'
>>> os.close(fd) // 通常建議用和打開對應(yīng)的方式關(guān)閉。
文件對象 fileno() 方法返回其對應(yīng)的文件描述符。
Python 對臨時文件的支持算是我所見過語言中最豐富的。通常建議使用 NamedTemporaryFile,其他可以忽略。
>>> import tempfile, os.path
>>> tmp = tempfile.NamedTemporaryFile()
>>> tmp.name
'/var/folders/r2/4vkjhz6s6lz02hk6nh2qb99c0000gn/T/tmpYYB6p3'
>>> os.path.exists(tmp.name)
True
>>> tmp.close()
>>> os.path.exists(tmp.name)
False
默認(rèn)使用系統(tǒng)臨時目錄和前綴,當(dāng)然也可以指定不同的配置。
>>> with tempfile.NamedTemporaryFile(prefix = "xxx_", suffix = ".tmp", dir = ".") as f:
... print f.name
...
/Users/yuhen/test/xxx_SL3apY.tmp
與臨時文件有關(guān)的函數(shù)還有:
>>> tempfile.gettempdir()
'/var/folders/r2/4vkjhz6s6lz02hk6nh2qb99c0000gn/T'
>>> tempfile.gettempprefix()
'tmp'
>>> d = tempfile.mkdtemp(); d
'/var/folders/r2/4vkjhz6s6lz02hk6nh2qb99c0000gn/T/tmpE_bRWd'
>>> os.path.exists(d)
True
>>> os.removedirs(d)
>>> fd, name = tempfile.mkstemp()
>>> os.write(fd, "123\n")
4
>>> os.close(fd)
>>> os.path.exists(name)
True
>>> os.remove(name)
常用函數(shù)列表:
http://wiki.jikexueyuan.com/project/the-python-study-notes-second-edition/images/5.png" alt="" />
http://wiki.jikexueyuan.com/project/the-python-study-notes-second-edition/images/6.png" alt="" />
拼接的目錄看上亂糟糟讓人煩心。
>>> os.path.normpath("./../a/b/../c")
'../a/c'
展開用戶根路徑,或者包含系統(tǒng)環(huán)境變量的路徑。
>>> os.path.expanduser("~/.vimrc")
'/Users/yuhen/.vimrc'
>>> os.path.expandvars("$HOME/.vimrc")
'/Users/yuhen/.vimrc'
除非只要擴展名,否則還是先用 basename 將路徑去掉。
>>> os.path.splitext(os.path.basename("/usr/local/lib/libevent.a"))
('libevent', '.a')
常用函數(shù)列表:
http://wiki.jikexueyuan.com/project/the-python-study-notes-second-edition/images/7.png" alt="" />
http://wiki.jikexueyuan.com/project/the-python-study-notes-second-edition/images/8.png" alt="" />
迭代 walk,返回 "(路徑,子目錄列表,文件列表)",可配合 fnmatch 做通配符過濾。
>>> for path, dirs, files in os.walk("."):
... for f in files:
... if fnmatch.fnmatch(f, "*.py"):
... print os.path.join(path, f)
./main.py
./bak/amqplib_test.py
./bak/eventlet_test.py
./bak/extract_text.py
./bak/fabric_test.py
如果僅操作當(dāng)前目錄,可以用 glob 代替 listdir,前者支持通配符。
>>> glob.glob("./bak/[rs]*.py") # 迭代器版本: iglob
['./bak/redis_test.py', './bak/socket_test.py']
如目錄中還有文件存在,removedirs 會拋出異常。建議用 shutil.rmtree() 代替,注意參數(shù)區(qū)別。
>>> os.makedirs("./a/b/c")
>>> open("./a/b/c/test.txt", "w").write("abc")
>>> os.removedirs("./a/b/c")
OSError: [Errno 66] Directory not empty: './a/b/c'
>>> import shutil
>>> shutil.rmtree("./a")
某些時候,需要先測試文件是否擁有某些權(quán)限。
>>> os.access("a.txt", os.W_OK)
True
都是哪些人需要修改文件時間?
>>> stat -x a.txt
File: "a.txt"
Size: 0 FileType: Regular File
Mode: (0644/-rw-r--r--) Uid: ( 501/ yuhen) Gid: ( 20/ staff)
Device: 1,2 Inode: 5111644 Links: 1
Access: Mon Mar 25 17:43:01 2013
Modify: Mon Mar 25 17:43:01 2013
Change: Mon Mar 25 17:43:01 2013
>>> atime = time.mktime(datetime.datetime(2010, 10, 1).utctimetuple())
>>> mtime = time.mktime(datetime.datetime(2010, 11, 2).utctimetuple())
>>> os.utime("a.txt", (atime, mtime))
>>> os.stat("a.txt").st_atime == atime
True
獲取文件權(quán)限信息時,別忘了轉(zhuǎn)換成八進(jìn)制。
>>> oct(os.stat("a.txt").st_mode)
'0100644'
常用函數(shù)列表:
http://wiki.jikexueyuan.com/project/the-python-study-notes-second-edition/images/9.png" alt="" />
copytree 可以指定多個忽略通配符,且必須確保目標(biāo)路徑不存在。
>>> shutil.copytree("./bak", "./b/bak", ignore = shutil.ignore_patterns("*.pyc",
"*.bak"))