如何建設(shè)網(wǎng)站平臺企業(yè)seo優(yōu)化服務(wù)
文章目錄
- 一.IO編程是什么
- 二.文件讀寫
- 1.讀取文件
- 2.file-like Object
- 二進制文件
- 字符編碼
- 3.寫文件
- file對象的常用函數(shù)
- 常見標識符
- 三.StringIO和BytesIO
- 1.StringIO
- 2.BytesIO
- 四.操作文件和目錄
- 五.序列化和反序列化
- 1.pickle.dumps()
- 2.pickle.loads()
- 3.JSON
一.IO編程是什么
IO在計算機中指Input/Output,也就是輸入和輸出。由于程序和運行時數(shù)據(jù)是在內(nèi)存中駐留,由CPU這個超快的計算核心來執(zhí)行,涉及到數(shù)據(jù)交換的地方,通常是磁盤、網(wǎng)絡(luò)
等,就需要IO接口
。
比如你打開瀏覽器,訪問新浪首頁,瀏覽器這個程序就需要通過網(wǎng)絡(luò)IO
獲取新浪的網(wǎng)頁。瀏覽器首先會發(fā)送數(shù)據(jù)給新浪服務(wù)器,告訴它我想要首頁的HTML,這個動作是往外發(fā)數(shù)據(jù),叫Output,隨后新浪服務(wù)器把網(wǎng)頁發(fā)過來,這個動作是從外面接收數(shù)據(jù),叫Input。所以,通常,程序完成IO操作會有Input和Output兩個數(shù)據(jù)流。當然也有只用一個的情況,比如,從磁盤讀取文件到內(nèi)存,就只有Input操作,反過來,把數(shù)據(jù)寫到磁盤文件里,就只是一個Output操作。
- IO編程中,
Stream(流)
是一個很重要的概念,可以把流想象成一個水管,數(shù)據(jù)就是水管里的水,但是只能單向流動。Input Stream就是數(shù)據(jù)從外面(磁盤、網(wǎng)絡(luò))流進內(nèi)存,Output Stream就是數(shù)據(jù)從內(nèi)存流到外面去。對于瀏覽網(wǎng)頁來說,瀏覽器和新浪服務(wù)器之間至少需要建立兩根水管,才可以既能發(fā)數(shù)據(jù),又能收數(shù)據(jù)。
由于CPU和內(nèi)存的速度遠遠高于外設(shè)的速度,所以,在IO編程中,就存在速度嚴重不匹配的問題。舉個例子來說,比如要把100M的數(shù)據(jù)寫入磁盤,CPU輸出100M的數(shù)據(jù)只需要0.01秒,可是磁盤要接收這100M數(shù)據(jù)可能需要10秒,怎么辦呢?有兩種辦法:
-
第一種是CPU等著,也就是程序暫停執(zhí)行后續(xù)代碼,等100M的數(shù)據(jù)在10秒后寫入磁盤,再接著往下執(zhí)行,這種模式稱為同步IO;
-
另一種方法是CPU不等待,只是告訴磁盤,“您老慢慢寫,不著急,我接著干別的事去了”,于是,后續(xù)代碼可以立刻接著執(zhí)行,這種模式稱為異步IO。
同步和異步的區(qū)別就在于是否等待IO執(zhí)行的結(jié)果。
-
好比你去麥當勞點餐,你說“來個漢堡”,服務(wù)員告訴你,對不起,漢堡要現(xiàn)做,需要等5分鐘,于是你站在收銀臺前面等了5分鐘,拿到漢堡再去逛商場,這是同步IO。
- 你說“來個漢堡”,服務(wù)員告訴你,漢堡需要等5分鐘,你可以先去逛商場,等做好了,我們再通知你,這樣你可以立刻去干別的事情(逛商場),這是異步IO。
很明顯,使用異步IO來編寫程序性能會遠遠高于同步IO,但是異步IO的缺點是編程模型復雜。
- 想想看,你得知道什么時候通知你“漢堡做好了”,而通知你的方法也各不相同。
如果是服務(wù)員跑過來找到你,這是回調(diào)模式
- 如果服務(wù)員發(fā)短信通知你,
你就得不停地檢查手機,這是輪詢模式
??傊?#xff0c;異步IO的復雜度遠遠高于同步IO。
操作IO的能力都是由操作系統(tǒng)提供的,每一種編程語言都會把操作系統(tǒng)提供的低級C接口封裝起來方便使用,Python也不例外。我們后面會詳細討論Python的IO編程接口。
注意,本章的IO編程都是同步模式,異步IO由于復雜度太高,后續(xù)涉及到服務(wù)器端程序開發(fā)時我們再討論。
二.文件讀寫
1.讀取文件
-
使用Python內(nèi)置的
open()
函數(shù),傳入文件名和標示符
:f = open('/data/est.txt', 'r')
-
標示符
'r'
表示讀,如果文件不存在,open()函數(shù)就會拋出一個IOError錯誤
,表示文件不存在:Traceback (most recent call last):File "<stdin>", line 1, in <module> FileNotFoundError: [Errno 2] No such file or directory: '/data/est.txt'
-
-
如果文件打開成功,調(diào)用
read()
方法可以一次讀取文件的全部內(nèi)容
,Python把內(nèi)容讀到內(nèi)存,用一個str對象
表示:f.read() #'Hello, world!'
-
調(diào)用close()方法關(guān)閉文件
- 文件使用完畢后必須關(guān)閉,因為
文件對象會占用操作系統(tǒng)的資源
,并且操作系統(tǒng)同一時間能打開的文件數(shù)量也是有限的:
f.close()
- 文件使用完畢后必須關(guān)閉,因為
-
文件讀寫可能產(chǎn)生
IOError
,一旦出錯,后面的f.close()
就不會調(diào)用。為了保證無論是否出錯都能正確地關(guān)閉文件,我們可以使用try ... finally
來實現(xiàn):try:f = open('/data/file.txt', 'r')print(f.read()) finally:if f:f.close()
-
每次都這么寫實在太繁瑣,所以,Python引入了
with語句
來自動幫我們調(diào)用close()方法:with open('/data/file.txt', 'r') as f:print(f.read())
-
-
調(diào)用
read()
會一次性讀取文件的全部內(nèi)容,如果文件有10G,內(nèi)存就爆了,所以,要保險起見,可以反復調(diào)用read(size)方法,每次最多讀取size個字節(jié)的內(nèi)容
。- 另外,調(diào)用
readline()可以每次讀取一行內(nèi)容
,調(diào)用readlines()一次讀取所有內(nèi)容并按行返回list
- 另外,調(diào)用
建議:如果文件很小,read()
一次性讀取最方便;如果不能確定文件大小,反復調(diào)用read(size)
比較保險;如果是配置文件,調(diào)用readlines()
最方便:
2.file-like Object
像open()函數(shù)返回的這種有個read()方法的對象,在Python中統(tǒng)稱為file-like Object
。除了file外,還可以是內(nèi)存的字節(jié)流,網(wǎng)絡(luò)流,自定義流等等
。file-like Object不要求從特定類繼承,只要寫個read()方法就行。
- StringIO就是在內(nèi)存中創(chuàng)建的file-like Object,常用作
臨時緩沖
。
二進制文件
要讀取二進制文件,比如圖片、視頻等等,用'rb'模式
打開文件即可:
f = open('/Users/michael/test.jpg', 'rb')
f.read()
`b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # `十六進制表示的字節(jié)
字符編碼
要讀取非UTF-8編碼
的文本文件,需要給open()函數(shù)傳入encoding參數(shù)
,例如,讀取GBK編碼的文件:
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
f.read()
-
遇到有些
編碼不規(guī)范的文件
,你可能會遇到UnicodeDecodeError
,因為在文本文件中可能夾雜了一些非法編碼
的字符。- 這種場景open()函數(shù)還接收一個
errors參數(shù)
,表示如果遇到編碼錯誤后如何處理。最簡單的方式是直接忽略:
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
- 這種場景open()函數(shù)還接收一個
3.寫文件
寫文件和讀文件是一樣的,唯一區(qū)別是調(diào)用open()
函數(shù)時,傳入標識符'w'或者'wb'
表示寫文本文件
或寫二進制文件
:
- 以’w’模式寫入文件時,如果文件已存在,會
直接覆蓋(相當于刪掉后新寫入一個文件)
。 - 如果我們希望追加到文件末尾怎么辦?可以傳入
'a'以追加(append)模式寫入
。f = open('/data/test.txt', 'w') f.write('Hello, world!') f.close()
- 可以反復調(diào)用
write()
來寫入文件,但是務(wù)必要調(diào)用close()
來關(guān)閉文件。- 當我們寫文件時,操作系統(tǒng)往往
不會立刻把數(shù)據(jù)寫入磁盤,而是放到內(nèi)存緩存起來,空閑的時候再慢慢寫入
。- 只有調(diào)用close()方法時,
操作系統(tǒng)才保證把沒有寫入的數(shù)據(jù)全部寫入磁盤
。 - 忘記調(diào)用
close()
的后果是數(shù)據(jù)可能只寫了一部分到磁盤,剩下的丟失了。所以,還是用with語句
來得保險:
- 只有調(diào)用close()方法時,
with open('/Users/michael/test.txt', 'w') as f:f.write('Hello, world!')
- 當我們寫文件時,操作系統(tǒng)往往
file對象的常用函數(shù)
常見標識符
三.StringIO和BytesIO
- StringIO和BytesIO是在內(nèi)存中操作str和bytes的方法,使得和讀寫文件具有一致的接口。
1.StringIO
StringIO顧名思義就是在內(nèi)存中讀寫str。
-
先創(chuàng)建一個StringIO,然后,像文件一樣寫入即可:
from io import StringIOf = StringIO() f.write('hello') # 5 f.write(' ') # 1 f.write('world!') # 6 print(f.getvalue()) # hello world!
getvalue()方法
用于獲得寫入后的str。
-
讀取StringIO,可以用一個
str初始化StringIO
,然后,像讀文件一樣讀取:from io import StringIOf = StringIO('Hello!\nHi!\nGoodbye!') while True:s = f.readline()if s == '': breakprint(s.strip())# Hello! # Hi! # Goodbye!
2.BytesIO
如果要操作二進制數(shù)據(jù),就需要使用BytesIO。
-
BytesIO實現(xiàn)了在內(nèi)存中讀寫bytes,創(chuàng)建一個BytesIO,然后寫入一些bytes:
寫入的不是str,而是經(jīng)過UTF-8編碼的bytes。
from io import BytesIO f = BytesIO() f.write('中文'.encode('utf-8')) #6print(f.getvalue()) #b'\xe4\xb8\xad\xe6\x96\x87'
-
初始化一個BytesIO,然后,像讀文件一樣讀取:
from io import BytesIO f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87') f.read() #b'\xe4\xb8\xad\xe6\x96\x87'
四.操作文件和目錄
如果要在Python程序中執(zhí)行這些目錄和文件的命令dir、cp、del操作怎么辦?
- Python內(nèi)置的
os模塊
也可以直接調(diào)用操作系統(tǒng)提供的接口函數(shù)。
#導入os模塊
import os#操作系統(tǒng)類型 如果是posix,說明系統(tǒng)是Linux、Unix或Mac OS X,如果是nt,就是Windows系統(tǒng)。
print(os.name)# 查看當前目錄的絕對路徑:
print(os.path.abspath('.'))
# 'E:\29_resources_python\study'#把兩個路徑合成一個時,不要直接拼字符串,而要通過os.path.join()函數(shù),
#這樣可以正確處理不同操作系統(tǒng)的路徑分隔符。在Linux/Unix/Mac下,os.path.join()返回這樣的字符串
print(os.path.join('E:/29_resources_python/study/', 'testdir'))#創(chuàng)建一個目錄:
os.mkdir('E:/29_resources_python/study/testdir')#刪掉一個目錄:
os.rmdir('E:/29_resources_python/study/testdir')#對文件重命名:
os.rename('E:/29_resources_python/study/test1', 'test.py')
#刪掉文件:
os.remove('test.py')
- os模塊中不存在復制文件功能
-
因是復制文件并非由操作系統(tǒng)提供的系統(tǒng)調(diào)用。我們通過文件讀寫可以完成文件復制,只不過要多寫很多代碼。
- 幸運的是
shutil模塊
提供了copyfile()的函數(shù)
,你還可以在shutil模塊中找到很多實用函數(shù),它們可以看做是os模塊的補充
。
- 幸運的是
-
五.序列化和反序列化
-
把變量從內(nèi)存中變成可存儲或傳輸?shù)倪^程稱之為
序列化
,序列化之后,就可以寫入磁盤,或者通過網(wǎng)絡(luò)傳輸?shù)絼e的機器上。 -
反過來,把變量內(nèi)容從序列化的對象重新讀到內(nèi)存里稱之為
反序列化
,Python提供了pickle模塊
來實現(xiàn)序列化。
1.pickle.dumps()
-
pickle.dumps()
方法可以任意對象序列化成一個bytes,然后,就可以把這個bytes寫入文件。import pickle d = dict(name='Bob', age=20, score=88) print(pickle.dumps(d)) #執(zhí)行結(jié)果 #b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
- 或者使用
pickle.dump()
直接把對象序列化后寫入一個指定文件中
f = open('dump.txt', 'wb') pickle.dump(d, f) f.close()
- 或者使用
2.pickle.loads()
-
當我們要把對象從磁盤讀到內(nèi)存時,可以先把內(nèi)容讀到一個·bytes·,然后用
pickle.loads()
方法反序列化出對象,也可以直接用·pickle.load()·方法從一個文件直接反序列化出對象。f = open('dump.txt', 'rb') d = pickle.load(f) f.close() print(d) #{'age': 20, 'score': 88, 'name': 'Bob'}
3.JSON
-
將數(shù)據(jù)序列化為JSON,可以方便地存儲到磁盤或者通過網(wǎng)絡(luò)傳輸。JSON不僅是標準格式,并且比XML更快,而且可以直接在Web頁面中讀取,非常方便。
- JSON表示的對象就是標準的JavaScript語言的對象,JSON和Python內(nèi)置的數(shù)據(jù)類型對應如下:
Python內(nèi)置的json模塊
提供了非常完善的Python對象到JSON格式的轉(zhuǎn)換。
Python序列化為Json對象
-
dumps():方法返回一個str,
內(nèi)容就是標準的JSON
-
dump():方法可以直接把JSON寫入一個文件中
import json d = dict(name='Bob', age=20, score=88) json.dumps(d) #'{"age": 20, "score": 88, "name": "Bob"}'
JSON反序列化為Python對象
-
loads():JSON的字符串反序列化
-
load():從文件中讀取字符串并反序列化:
import json json_str = '{"age": 20, "score": 88, "name": "Bob"}' json.loads(json_str) #{'age': 20, 'score': 88, 'name': 'Bob'}
Class轉(zhuǎn)JSON
-
對象實例直接轉(zhuǎn)class會報錯報錯,原因是Student對象不是一個可序列化為JSON的對象。
import jsonclass Student(object):def __init__(self, name, age, score):self.name = nameself.age = ageself.score = scores = Student('Bob', 20, 88) print(json.dumps(s))
Traceback (most recent call last):... TypeError: <__main__.Student object at 0x10603cc50> is not JSON serializable
-
默認情況下,
dumps()
方法不知道如何將Student實例變?yōu)橐粋€JSON的{}對象。我需要為Student專門寫一個轉(zhuǎn)換函數(shù),再把函數(shù)傳進去即可:def student2dict(std):return {'name': std.name,'age': std.age,'score': std.score}
-
需要Student實例首先被
student2dict()函數(shù)轉(zhuǎn)換成dict,然后再被順利序列化為JSON
:s = Student('Bob', 20, 88) print(json.dumps(s, default=student2dict)) # {"age": 20, "name": "Bob", "score": 88}
-
也可以把任意class的實例變?yōu)閐ict:
print(json.dumps(s, default=lambda obj: obj.__dict__)) # {"age": 20, "name": "Bob", "score": 88}
- 因為通常class的實例都有一個
__dict__屬性,它就是一個dict,用來存儲實例變量
。也有少數(shù)例外,比如定義了__slots__的class
。
- 因為通常class的實例都有一個
-
JSON轉(zhuǎn)Class
先用loads()方法
首先轉(zhuǎn)換出一個dict對象
,然后傳入的轉(zhuǎn)換函數(shù)負責 將dict轉(zhuǎn)換為Student實例:
def dict2student(d):return Student(d['name'], d['age'], d['score'])
json_str = '{"age": 20, "score": 88, "name": "Bob"}'
print(json.loads(json_str, object_hook=dict2student))
#<__main__.Student object at 0x000001C3B0BD9CD0>