1. 程式人生 > >【Python】學習筆記——-10、IO程式設計

【Python】學習筆記——-10、IO程式設計

一、什麼是IO程式設計

IO在計算機中指Input/Output,也就是輸入和輸出。由於程式和執行時資料是在記憶體中駐留,由CPU這個超快的計算核心來執行,涉及到資料交換的地方,通常是磁碟、網路等,就需要IO介面。

比如你開啟瀏覽器,訪問新浪首頁,瀏覽器這個程式就需要通過網路IO獲取新浪的網頁。瀏覽器首先會發送資料給新浪伺服器,告訴它我想要首頁的HTML,這個動作是往外發資料,叫Output,隨後新浪伺服器把網頁發過來,這個動作是從外面接收資料,叫Input。所以,通常,程式完成IO操作會有Input和Output兩個資料流。當然也有隻用一個的情況,比如,從磁碟讀取檔案到記憶體,就只有Input操作,反過來,把資料寫到磁碟檔案裡,就只是一個Output操作。

IO程式設計中,Stream(流)是一個很重要的概念,可以把流想象成一個水管,資料就是水管裡的水,但是隻能單向流動。Input Stream就是資料從外面(磁碟、網路)流進記憶體,Output Stream就是資料從記憶體流到外面去。對於瀏覽網頁來說,瀏覽器和新浪伺服器之間至少需要建立兩根水管,才可以既能發資料,又能收資料。

由於CPU和記憶體的速度遠遠高於外設的速度,所以,在IO程式設計中,就存在速度嚴重不匹配的問題。舉個例子來說,比如要把100M的資料寫入磁碟,CPU輸出100M的資料只需要0.01秒,可是磁碟要接收這100M資料可能需要10秒,怎麼辦呢?有兩種辦法:

第一種是CPU等著,也就是程式暫停執行後續程式碼,等100M的資料在10秒後寫入磁碟,再接著往下執行,這種模式稱為同步IO;

另一種方法是CPU不等待,只是告訴磁碟,“您老慢慢寫,不著急,我接著幹別的事去了”,於是,後續程式碼可以立刻接著執行,這種模式稱為非同步IO。

同步和非同步的區別就在於是否等待IO執行的結果。好比你去麥當勞點餐,你說“來個漢堡”,服務員告訴你,對不起,漢堡要現做,需要等5分鐘,於是你站在收銀臺前面等了5分鐘,拿到漢堡再去逛商場,這是同步IO。

你說“來個漢堡”,服務員告訴你,漢堡需要等5分鐘,你可以先去逛商場,等做好了,我們再通知你,這樣你可以立刻去幹別的事情(逛商場),這是非同步IO。

很明顯,使用非同步IO來編寫程式效能會遠遠高於同步IO,但是非同步IO的缺點是程式設計模型複雜。想想看,你得知道什麼時候通知你“漢堡做好了”,而通知你的方法也各不相同。如果是服務員跑過來找到你,這是回撥模式,如果服務員發簡訊通知你,你就得不停地檢查手機,這是輪詢模式。總之,非同步IO的複雜度遠遠高於同步IO。

操作IO的能力都是由作業系統提供的,每一種程式語言都會把作業系統提供的低階C介面封裝起來方便使用,Python也不例外。我們後面會詳細討論Python的IO程式設計介面。

注意,本章的IO程式設計都是同步模式,非同步IO由於複雜度太高,後續涉及到伺服器端程式開發時我們再討論。


二、檔案讀寫

讀寫檔案是最常見的IO操作。Python內建了讀寫檔案的函式,用法和C是相容的。

讀寫檔案前,我們先必須瞭解一下,在磁碟上讀寫檔案的功能都是由作業系統提供的,現代作業系統不允許普通的程式直接操作磁碟,所以,讀寫檔案就是請求作業系統開啟一個檔案物件(通常稱為檔案描述符),然後,通過作業系統提供的介面從這個檔案物件中讀取資料(讀檔案),或者把資料寫入這個檔案物件(寫檔案)。

讀檔案

要以讀檔案的模式開啟一個檔案物件,使用Python內建的open()函式,傳入檔名和標示符:

>>> f = open('/Users/michael/test.txt', 'r')

標示符'r'表示讀,這樣,我們就成功地打開了一個檔案。

如果檔案不存在,open()函式就會丟擲一個IOError的錯誤,並且給出錯誤碼和詳細的資訊告訴你檔案不存在:

>>> f=open('/Users/michael/notfound.txt', 'r')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt'

如果檔案開啟成功,接下來,呼叫read()方法可以一次讀取檔案的全部內容,Python把內容讀到記憶體,用一個str物件表示:

>>> f.read()
'Hello, world!'

最後一步是呼叫close()方法關閉檔案。檔案使用完畢後必須關閉,因為檔案物件會佔用作業系統的資源,並且作業系統同一時間能開啟的檔案數量也是有限的:

>>> f.close()

由於檔案讀寫時都有可能產生IOError,一旦出錯,後面的f.close()就不會呼叫。所以,為了保證無論是否出錯都能正確地關閉檔案,我們可以使用try ... finally來實現:

try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()

但是每次都這麼寫實在太繁瑣,所以,Python引入了with語句來自動幫我們呼叫close()方法:

with open('/path/to/file', 'r') as f:
    print(f.read())

這和前面的try ... finally是一樣的,但是程式碼更佳簡潔,並且不必呼叫f.close()方法。

呼叫read()會一次性讀取檔案的全部內容,如果檔案有10G,記憶體就爆了,所以,要保險起見,可以反覆呼叫read(size)方法,每次最多讀取size個位元組的內容。另外,呼叫readline()可以每次讀取一行內容,呼叫readlines()一次讀取所有內容並按行返回list。因此,要根據需要決定怎麼呼叫。

如果檔案很小,read()一次性讀取最方便;如果不能確定檔案大小,反覆呼叫read(size)比較保險;如果是配置檔案,呼叫readlines()最方便:

for line in f.readlines():
    print(line.strip()) # 把末尾的'\n'刪掉

file-like Object

open()函式返回的這種有個read()方法的物件,在Python中統稱為file-like Object。除了file外,還可以是記憶體的位元組流,網路流,自定義流等等。file-like Object不要求從特定類繼承,只要寫個read()方法就行。

StringIO就是在記憶體中建立的file-like Object,常用作臨時緩衝。

二進位制檔案

前面講的預設都是讀取文字檔案,並且是UTF-8編碼的文字檔案。要讀取二進位制檔案,比如圖片、視訊等等,用'rb'模式開啟檔案即可:

>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六進位制表示的位元組

字元編碼

要讀取非UTF-8編碼的文字檔案,需要給open()函式傳入encoding引數,例如,讀取GBK編碼的檔案:

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'測試'

遇到有些編碼不規範的檔案,你可能會遇到UnicodeDecodeError,因為在文字檔案中可能夾雜了一些非法編碼的字元。遇到這種情況,open()函式還接收一個errors引數,表示如果遇到編碼錯誤後如何處理。最簡單的方式是直接忽略:

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')

寫檔案

寫檔案和讀檔案是一樣的,唯一區別是呼叫open()函式時,傳入識別符號'w'或者'wb'表示寫文字檔案或寫二進位制檔案:

>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()

你可以反覆呼叫write()來寫入檔案,但是務必要呼叫f.close()來關閉檔案。當我們寫檔案時,作業系統往往不會立刻把資料寫入磁碟,而是放到記憶體快取起來,空閒的時候再慢慢寫入。只有呼叫close()方法時,作業系統才保證把沒有寫入的資料全部寫入磁碟。忘記呼叫close()的後果是資料可能只寫了一部分到磁碟,剩下的丟失了。所以,還是用with語句來得保險:

with open('/Users/michael/test.txt', 'w') as f:
    f.write('Hello, world!')

要寫入特定編碼的文字檔案,請給open()函式傳入encoding引數,將字串自動轉換成指定編碼。

小結

在Python中,檔案讀寫是通過open()函式開啟的檔案物件完成的。使用with語句操作檔案IO是個好習慣。


三、StringIO和BytesIO

StringIO

很多時候,資料讀寫不一定是檔案,也可以在記憶體中讀寫。

StringIO顧名思義就是在記憶體中讀寫str。

要把str寫入StringIO,我們需要先建立一個StringIO,然後,像檔案一樣寫入即可:

>>> from io import StringIO
>>> f = 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 StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!

BytesIO

StringIO操作的只能是str,如果要操作二進位制資料,就需要使用BytesIO。

BytesIO實現了在記憶體中讀寫bytes,我們建立一個BytesIO,然後寫入一些bytes:

>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

請注意,寫入的不是str,而是經過UTF-8編碼的bytes。

和StringIO類似,可以用一個bytes初始化BytesIO,然後,像讀檔案一樣讀取:

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

小結

StringIO和BytesIO是在記憶體中操作str和bytes的方法,使得和讀寫檔案具有一致的介面。


四、操作檔案和目錄

如果我們要操作檔案、目錄,可以在命令列下面輸入作業系統提供的各種命令來完成。比如dircp等命令。

如果要在Python程式中執行這些目錄和檔案的操作怎麼辦?其實作業系統提供的命令只是簡單地呼叫了作業系統提供的介面函式,Python內建的os模組也可以直接呼叫作業系統提供的介面函式。

開啟Python互動式命令列,我們來看看如何使用os模組的基本功能:

>>> import os
>>> os.name # 作業系統型別
'posix'

如果是posix,說明系統是LinuxUnixMac OS X,如果是nt,就是Windows系統。

要獲取詳細的系統資訊,可以呼叫uname()函式:

>>> os.uname()
posix.uname_result(sysname='Darwin', nodename='MichaelMacPro.local', release='14.3.0', version='Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64', machine='x86_64')

注意uname()函式在Windows上不提供,也就是說,os模組的某些函式是跟作業系統相關的。

環境變數

在作業系統中定義的環境變數,全部儲存在os.environ這個變數中,可以直接檢視:

>>> os.environ
environ({'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...})

要獲取某個環境變數的值,可以呼叫os.environ.get('key')

>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'

操作檔案和目錄

操作檔案和目錄的函式一部分放在os模組中,一部分放在os.path模組中,這一點要注意一下。檢視、建立和刪除目錄可以這麼呼叫:

# 檢視當前目錄的絕對路徑:
>>> os.path.abspath('.')
'/Users/michael'
# 在某個目錄下建立一個新目錄,首先把新目錄的完整路徑表示出來:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然後建立一個目錄:
>>> os.mkdir('/Users/michael/testdir')
# 刪掉一個目錄:
>>> os.rmdir('/Users/michael/testdir')

把兩個路徑合成一個時,不要直接拼字串,而要通過os.path.join()函式,這樣可以正確處理不同作業系統的路徑分隔符。在Linux/Unix/Mac下,os.path.join()返回這樣的字串:

part-1/part-2

而Windows下會返回這樣的字串:

part-1\part-2

同樣的道理,要拆分路徑時,也不要直接去拆字串,而要通過os.path.split()函式,這樣可以把一個路徑拆分為兩部分,後一部分總是最後級別的目錄或檔名:

>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')

os.path.splitext()可以直接讓你得到副檔名,很多時候非常方便:

>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')

這些合併、拆分路徑的函式並不要求目錄和檔案要真實存在,它們只對字串進行操作。

檔案操作使用下面的函式。假定當前目錄下有一個test.txt檔案:

# 對檔案重新命名:
>>> os.rename('test.txt', 'test.py')
# 刪掉檔案:
>>> os.remove('test.py')

但是複製檔案的函式居然在os模組中不存在!原因是複製檔案並非由作業系統提供的系統呼叫。理論上講,我們通過上一節的讀寫檔案可以完成檔案複製,只不過要多寫很多程式碼。

幸運的是shutil模組提供了copyfile()的函式,你還可以在shutil模組中找到很多實用函式,它們可以看做是os模組的補充。

最後看看如何利用Python的特性來過濾檔案。比如我們要列出當前目錄下的所有目錄,只需要一行程式碼:

>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]

要列出所有的.py檔案,也只需一行程式碼:

>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']

是不是非常簡潔?

小結

Python的os模組封裝了作業系統的目錄和檔案操作,要注意這些函式有的在os模組中,有的在os.path模組中。


五、序列化

在程式執行的過程中,所有的變數都是在記憶體中,比如,定義一個dict:

d = dict(name='Bob', age=20, score=88)

可以隨時修改變數,比如把name改成'Bill',但是一旦程式結束,變數所佔用的記憶體就被作業系統全部回收。如果沒有把修改後的'Bill'儲存到磁碟上,下次重新執行程式,變數又被初始化為'Bob'

我們把變數從記憶體中變成可儲存或傳輸的過程稱之為序列化,在Python中叫pickling,在其他語言中也被稱之為serialization,marshalling,flattening等等,都是一個意思。

序列化之後,就可以把序列化後的內容寫入磁碟,或者通過網路傳輸到別的機器上。

反過來,把變數內容從序列化的物件重新讀到記憶體裡稱之為反序列化,即unpickling。

Python提供了pickle模組來實現序列化。

首先,我們嘗試把一個物件序列化並寫入檔案:

>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
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.dumps()方法把任意物件序列化成一個bytes,然後,就可以把這個bytes寫入檔案。或者用另一個方法pickle.dump()直接把物件序列化後寫入一個file-like Object:

>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()

看看寫入的dump.txt檔案,一堆亂七八糟的內容,這些都是Python儲存的物件內部資訊。

當我們要把物件從磁碟讀到記憶體時,可以先把內容讀到一個bytes,然後用pickle.loads()方法反序列化出物件,也可以直接用pickle.load()方法從一個file-like Object中直接反序列化出物件。我們開啟另一個Python命令列來反序列化剛才儲存的物件:

>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

變數的內容又回來了!

當然,這個變數和原來的變數是完全不相干的物件,它們只是內容相同而已。

Pickle的問題和所有其他程式語言特有的序列化問題一樣,就是它只能用於Python,並且可能不同版本的Python彼此都不相容,因此,只能用Pickle儲存那些不重要的資料,不能成功地反序列化也沒關係。

JSON

如果我們要在不同的程式語言之間傳遞物件,就必須把物件序列化為標準格式,比如XML,但更好的方法是序列化為JSON,因為JSON表示出來就是一個字串,可以被所有語言讀取,也可以方便地儲存到磁碟或者通過網路傳輸。JSON不僅是標準格式,並且比XML更快,而且可以直接在Web頁面中讀取,非常方便。

JSON表示的物件就是標準的JavaScript語言的物件,JSON和Python內建的資料型別對應如下:

JSON型別 Python型別
{} dict
[] list
"string" str

相關推薦

Python學習筆記——-10IO程式設計

一、什麼是IO程式設計 IO在計算機中指Input/Output,也就是輸入和輸出。由於程式和執行時資料是在記憶體中駐留,由CPU這個超快的計算核心來執行,涉及到資料交換的地方,通常是磁碟、網路等,就需要IO介面。 比如你開啟瀏覽器,訪問新浪首頁,瀏覽器這個程式就需要

python學習筆記10-ddt數據驅動

get args code imp content coo 文檔 AR ems DDT的使用 DDT包含類的裝飾器ddt和兩個方法裝飾器data(直接輸入測試數據),file_data(可以從json或者yaml中獲取測試數據) 只有yaml和yml結尾的文件以yaml形式

Python學習筆記——-20Web開發

最早的軟體都是執行在大型機上的,軟體使用者通過“啞終端”登陸到大型機上去執行軟體。後來隨著PC機的興起,軟體開始主要執行在桌面上,而資料庫這樣的軟體執行在伺服器端,這種Client/Server模式簡稱CS架構。 隨著網際網路的興起,人們發現,CS架構不適合Web,最大

Python學習筆記4-timemd5加密base64模塊

數據庫 sta 自己的 pri 時間 decode 不可 字符 ftime 1、time # 1、格式化好的時間 2018-1-14 16:42# 2、時間戳 是從unix元年到現在所有的秒數# 3、時間元組# 想時間戳和格式化好的時間互相轉換的話,都要先轉成時間元組,然後

Python學習筆記4-ossys模塊

etc 創建 記錄 body spa platform pri eas usr 1、os操作系統模塊 1 import os 2 print(os.getcwd()) #獲取當前工作目錄 3 # print(os.chdir("..")) #更改當前目錄.當前目錄

Python爬蟲學習筆記10多線程中的生產者消費者模式

其中 因此 問題 共享 and 生產者消費者模式 共享問題 由於 接下來 在多線程編程中,最經典的模式是生產者消費者模式。其中,生產者是專門用來生產數據的線程,它把數據存放在一個中間變量中;而消費者則從這個中間變量取出數據進行消費。由於生產者和消費者共享中間變量,這些變量大

Python學習筆記十三:函數的參數對應

color 筆記 屏幕 *args borde 基於 但是 all small 位置傳遞 我們在定義函數時候已經對函數進行了參數傳遞調用,但是那只是粗淺的位置傳遞 示例 def sum(a,b,c): d = a+b+c return d p

Python學習筆記4-內置函數

打印數字 筆記 轉換 查看 判斷 lis clas 函數 ted 1、內置函數 1 print(all([1,2,3,0])) #判斷可叠代對象裏面是否都為真:非零即真非空即真原則 2 print(any([1,2,3,0])) #判斷可叠代對象裏面是否有一個為真:非

Python學習筆記5-操作mysql數據庫pymysql

rod bsp phone utf8 charset delet 建立 包含 hone import pymysql#其他數據庫,比如oracle 模塊是pyoracle#1、鏈接數據庫mysq ip 端口號 密碼 賬戶 數據庫#2、建立遊標#3、執行sql#4、獲取結果#

Python學習筆記5-利用flask來mock接口

入口 輸出 端口號 delet app file log asc conf # 1、mock接口# import flask #python的輕量級的開發框架# # 接口,後臺服務的開發# # 在瀏覽器運行http://127.0.0.1:8080/get_user即可,或

Python學習筆記

數字型別 整數型別:沒有取值範圍限制 浮點數型別:浮點數精度受限,但精度很高,除科學計算外可視為不受限 複數型別:z = a + bj   實數部分:z.real  複數部分:z.imag

Python爬蟲學習筆記2urllib庫的基本使用

代理服務 cor proc 技術 origin car windows tpc -c urllib庫是python內置的實現HTTP請求的基本庫,通過它可以模擬瀏覽器的行為,向指定的服務器發送一個請求,並保存服務器返回的數據。 urlopen函數 函數原型:urlopen(

Python爬蟲學習筆記8-2MongoDB數據庫操作詳解

參考資料 adding ocl 切換 username 詳解 top .com min 上一篇學習筆記8-1中介紹了MySQL和MongoDB的安裝、啟動和配置,本節我們接著學習有關MongoDB的一些概念、基本操作和在python中的使用。 MongoDB常用概念 為更好

Python個人學習筆記---《Python遊戲程式設計入門》第二章小結挑戰習題(二)

問題:選取一個示列,例如,繪製線條示例,修改它以便用隨機的值繪製1000個線條。瞭解一下random庫和random.randint()函式。 最開始想法是,用pygame.draw.line(Surface, color, start_pos, end_pos, width) 來

Python個人學習筆記---《Python遊戲程式設計入門》第二章小結挑戰習題(三)

問題:繪製矩形示列是一個圍繞螢幕移動形狀的示列,任何時候,矩形碰到螢幕邊界時,矩形都會改變顏色。 把 每次碰撞時改變的顏色用列表來歸納並計算反彈次數作為索引是個不錯的思路。 程式碼如下: import sys import pygame from pygame.locals i

Python個人學習筆記--- 變數的基本使用

前言:程式就是用來處理資料的, 而變數就是用來儲存資料的。 文章目錄 01. 變數定義 02. 變數的型別 03.不同型別的變數之間的計算 04.變數的輸出 05.變數的格式化輸出 06.變數的命名 0

Python個人學習筆記--- 註釋

文章目錄 1.註釋的作用 2.單行註釋(行註釋) 2.1在程式碼後面增加的單行註釋 3.多行註釋(塊註釋) 4. 什麼時候需要註釋 1.註釋的作用 使用自己熟悉的語言,對程式中

Python個人學習筆記--- 編譯器

文章目錄 編譯器翻譯的方式有兩種: Python 源程式的基本概念 Python 其他直譯器 編譯器翻譯的方式有兩種: 1.編譯 2.解釋 兩者之間的區別在於翻譯的使勁點不同。 當編譯器以直譯器方式

深入理解JVM學習筆記——-1JVM基本結構

轉載自:https://blog.csdn.net/singit/article/details/54920387?utm_source=blogkpcl11   什麼是jvm?JVM的基本結構, 也就是概述。說是概述,內容很多,而且概念量也很大, 不過關於概念方面,你不用擔心,我完全有信心

FPGA學習筆記-IO引腳分配

Error (169029): Pin Key_1 is incompatible with I/O bank 6.  Pin uses I/O standard 2.5 V, which has a VCCIO requirement incompatible with that bank's VCCIO