Python基礎及進階內容已陸續更新!歡迎小伙伴們一起關注學習!
本篇文章和大家分享Python高階開發中詳細的IO操作、執行緒和行程操作!,建議先收藏之后慢慢學習!
目錄
寫在前面
一、Python輸入輸出——IO操作
1、檔案讀寫
(1)打開檔案
(2)寫入檔案
(3)讀取檔案
(4)關閉檔案
2、檔案系統操作
3、序列化和反序列化
(1)pickle
(2)JSON
二、讓你的代碼更加靈活——行程和執行緒操作
1、行程操作
2、執行緒操作
(1)執行緒鎖
寫在前面
Hello,你好呀!我是灰小猿,一個超會寫bug的程式猿!本想彪上一手好bug,奈何技術太差,只能茍且搞輸出!
近期和大家分享了很多關于Python入門進階相關的文章,幫助了很多小伙伴了解并深入的學習到了Python開發,在這里為大家安利上文章鏈接,有想學習的小伙伴可以收藏閱讀:
Python入門及進階技術:
【全網力薦】堪稱最易學的Python基礎入門教程
萬字長文爆肝Python基礎入門【第二彈、超詳細資料型別總結】
諾,你們要的Python進階來咯!【函式、類進階必備】
周末加班想摸魚?不如來點Python進階干貨呀!【超詳細迭代器、生成器、裝飾器使用教程】
秀!學妹看見都驚呆的Python小招數!【詳細語言特性使用教程】
常見報錯及解決方案:
全網最值得收藏的Python常見報錯及其解決方案,再也不用擔心遇到BUG了!
今天繼續和大家分享Python高階開發中詳細的IO操作、執行緒和行程操作!幫助你掌握在基礎進階之后又一高階技術!小伙伴們可以關注我一起學習呀!

一、Python輸入輸出——IO操作
1、檔案讀寫
(1)打開檔案
在進行檔案讀寫之前,有個重要的步驟——將檔案打開,同時指定針對檔案的讀寫模式,比如只讀、只寫、可讀可寫等等,只有先打開檔案才能對檔案進行讀寫操作,
打開檔案使用內置函式 open():
f = open('檔案路徑', 讀寫模式)
如:
f = open('/Users/obsession/text', 'w')
其中,讀寫模式 有以下常用選項:
'r':只讀,若檔案不存在則拋出FileNotFoundError例外'w':只寫,將覆寫所有原有內容,若檔案不存在則創建檔案'a':只寫,以追加的形式寫入內容,若檔案不存在則創建檔案'r+':可讀可寫,若檔案不存在則拋出FileNotFoundError例外'w+':可讀可寫,若檔案不存在則創建檔案'a+':可讀可寫,寫入時使用追加模式,若檔案不存在則創建檔案
以上所有讀寫模式都是基于文本內容的,如果想要讀寫二進制內容,可在上面的基礎上添加 'b' 模式,如 rb、'wb+',
open() 的回傳值為 file 物件,也就是這里的變數 f,利用這個物件,我們可以進行檔案讀寫,
上述打開方式默認使用 UTF-8 編碼,如果檔案內容并非 UTF-8 編碼,可以使用 encoding 引數指定編碼格式,如 f = open('/Users/obsession/text', 'w', encoding='gbk'),
(2)寫入檔案
寫入檔案使用:
length = f.write('內容')
>>> f = open(’/Users/obsession/text’, ‘w’)
>>> f.write(‘The quick brown fox jumps over the lazy dog’)
43
呼叫 f.write() 后將回傳寫入字符的長度,
(3)讀取檔案
讀取檔案使用:
content = f.read()
>>> f = open(’/Users/obsession/text’, ‘r’)
>>> f.read()
’The quick brown fox jumps over the lazy dog’
上例中將讀取檔案的所有內容,也可以指定要讀取內容的字符長度:
>>> f = open(’/Users/obsession/text’, ‘r’)
>>> f.read(30)
’The quick brown fox jumps over’
>>> f.read(30)
’ the lazy dog’
>>> f.read(30)
’’
此時將根據所指定的長度來讀取內容,注意觀察示例,每次呼叫 f.read(30) 時都是從上一次讀取的結束位置開始,來讀取新的內容,直至所有的內容被獲取完,之后再呼叫 f.read(30) 只會得到空字串 '',
還可以按行來讀取檔案,使用:
line = f.readline()
例如某檔案內容為
The quick brown fox
jumps over
the lazy dog
按行讀取檔案如下:
>>> f = open(’/Users/obsession/text’, ‘r’)
>>> f.readline()
’The quick brown fox\n’
>>> f.readline()
’jumps over\n’
>>> f.readline()
’the lazy dog’
>>> f.readline()
’’
按行讀取檔案還可以一次性將所有行讀出,然后放進串列里:
lines = f.readlines()
>>> f = open(’/Users/obsession/text’, ‘r’)
>>> f.readlines()
[‘The quick brown fox\n’, ‘jumps over\n’, ‘the lazy dog’]
(4)關閉檔案
每次打開檔案后,無論進行了多少讀寫操作,最終都一定要將檔案關閉,因為打開檔案會消耗相關系統資源(檔案描述符),不使用時應及時釋放,
關閉檔案使用:
f.close()
還有一種方式能自動關閉打開的檔案,那就是使用 with 陳述句:
with open('/Users/obsession/text', 'w') as f:
f.write('The quick brown fox jumps over the lazy dog')
open() 后的 file 物件會被 as 關鍵字賦予變數 f,和之前一樣,我們利用 f 進行檔案讀寫,
with 陳述句會在它的代碼塊執行完畢后,或代碼塊拋出例外時,自動關閉檔案,為我們省卻了 f.close() 步驟,
2、檔案系統操作
檔案系統操作需要使用內置的 os 模塊,
- 創建目錄
import os os.mkdir('/Users/obsession/test_dir') - 判斷路徑是否是一個目錄
os.path.isdir('/Users/obsession/test_dir') - 列舉目錄下的內容
os.listdir('/Users/obsession') - 洗掉目錄
os.rmdir('/Users/obsession/test_dir') - 創建檔案
創建檔案可以直接使用之前學過的open():f = open('/Users/obsession/test_file', 'w') f.close() - 判斷路徑是否是一個檔案
os.path.isfile('/Users/obsession/test_file') - 洗掉檔案
os.remove('/Users/obsession/test_file') - 重命名檔案
os.rename('/Users/obsession/test_file', 'test_file_02')
3、序列化和反序列化
程式運行時,產生的所有物件都位于記憶體之中,記憶體有個特點,那就是它是非持久的,如果程式運行結束或者計算機斷電,占用的記憶體將被清空,
有時,我們需要把程式運行時記憶體中的物件,持久化下來保存在檔案系統中,或者傳輸至網路,比如將這樣一個類的物件保存在檔案中:
class Pair:
def __init__(self, first, second):
self.first = first
self.second = second
pair = Pair(10, 20)
這就涉及到序列化和反序列化了,序列化是將記憶體中的物件轉換為可被存盤或可被傳輸的形式的程序,反序列化是將序列化后的內容恢復回記憶體中物件的程序,
(1)pickle
Python 中內置的 pickle 模塊用作序列化和反序列化,它的序列化結果是二進制形式,
序列化使用:
import pickle
some_bytes = pickle.dumps(物件)
>>> pair = Pair(10, 20)
>>> pickle.dumps(pair)
b’\x80\x03c__main__\nPair\nq\x00)\x81q\x01}q\x02(X\x05\x00\x00\x00firstq\x03K\nX\x06\x00\x00\x00secondq\x04K\x14ub.’
上面輸出的亂碼便是 pair 物件被序列化后的二進制,
對于剛才序列化后的結果,可以使用 pickle.loads() 將其反序列化回物件,如:
some_bytes = b'\x80\x03c__main__\nPair\nq\x00)\x81q\x01}q\x02(X\x05\x00\x00\x00firstq\x03K\nX\x06\x00\x00\x00secondq\x04K\x14ub.'
pair = pickle.loads(some_bytes)
此 pair 物件可以像之前一樣正常被使用:
>>> pair.first
10
>>> pair.second
20
也可以與 open() 相結合,將序列化的結果保存在檔案中,此時使用 pickle.dump()(注意與之前的 pickle.dumps() 不同):
with open('/Users/obsession/dump', 'wb') as f:
pickle.dump(pair, f)
從檔案中反序列化出物件,使用 pickle.load()(注意與之前的 pickle.loads() 不同):
with open('/Users/obsession/dump', 'rb') as f:
pair = pickle.load(f)
(2)JSON
pickle 使用 Python 專用的序列化格式,序列化后的結果無法做到跨語言使用,另外其序列化結果是二進制,不適合閱讀,
JSON 相對而言更加通用和流行,并且其結果為文本格式,更具可讀性,
同樣是剛才的 pair 物件,可以像這樣將它序列化為 JSON 字串:
import json
json_string = json.dumps(pair.__dict__)
>>> json_string
’{“first”: 10, “second”: 20}’
注意上面結果為字串型別,另外這里使用了 pair.__dict__ 來獲取包含所有 pair 屬性的字典,因為類物件不能直接用于 json.dumps() 序列化,而字典可以,
或者使用 default 引數,向 json.dumps() 告知如何進行從物件到字典的轉換,這樣便可以不使用 __dict__ 屬性,如下:
def pair_to_dict(pair):
return {
'first': pair.first,
'second': pair.second,
}
json_string = json.dumps(pair, default=pair_to_dict)
>>> json_string
’{“first”: 10, “second”: 20}’
從 JSON 反序列化為物件:
def dict_to_pair(d):
return Pair(d['first'], d['second'])
pair = json.loads(json_string, object_hook=dict_to_pair)
上述反序列化程序中,json.loads() 首先會將 JSON 字串反序列化為字典,然后使用 object_hook 引數進一步從字典轉換出 pair 物件,
與 pickle 相似,json 也可以與 open() 結合使用,將序列化的結果保存在檔案中:
with open('/Users/obsession/json', 'w') as f:
json.dump(pair, f, default=pair_to_dict)
或從檔案中反序列化出物件:
with open('/Users/obsession/json', 'r') as f:
pair = json.load(f, object_hook=dict_to_pair)
二、讓你的代碼更加靈活——行程和執行緒操作
行程和執行緒時作業系統所提供的,能讓程式在同一時間處理多個任務的方法,讓程式能夠做到「一心二用」,

1、行程操作
當我們運行一個程式時,這個程式的代碼會被作業系統加載記憶體中,并創建出一個行程來承載和運行它,簡單來說,每一個運行中的程式就是一個行程,這個行程被稱為主行程,
在主行程中,我們可以創建子行程來協助處理其它任務,這時主行程和子行程是并行運行的,子行程也可以有它的子行程,從而形成以主行程為根的一棵行程樹,
我們可以使用 multiprocessing.Process() 方法來創建行程:
import multiprocessing
p = multiprocessing.Process(target=目標函式, args=(目標函式的引數,))
用 start() 方法來啟動一個行程:
p.start()
來看個例子:
import multiprocessing
import os
def target_func():
print('子行程運行')
print('子行程 pid:', os.getpid())
print('子行程的 ppid:', os.getppid())
p = multiprocessing.Process(target=target_func)
p.start()
print('主行程運行')
print('主行程 pid:', os.getpid())
將上述代碼拷貝至檔案 process.py 中,執行下:
? ~ python3 process.py
主行程運行
主行程 pid: 13343
子行程運行
子行程 pid: 13344
子行程的 ppid: 13343
在這里例子中,
- 使用
multiprocessing.Process()來創建行程,并為該行程指定要執行的目標函式target_func,行程啟動后將執行該函式 - 使用
start()方法來啟動行程 - 使用
os.getpid()獲取行程的行程 ID,它是行程的唯一的標識,可用于區分行程 - 使用
os.getppid()獲取行程的父行程 ID,父行程是創建子行程的行程 - 主行程的
pid和子行程的ppid相同(因為主行程是該子行程的父行程)
另外可以看到,雖然子行程被創建并啟動,但子行程中的 print() 函式并未立即執行,反而是主行程中的 print() 函式先執行,這說明行程間的執行順序是不確定的,并非同步執行,
使用 join() 方法可以控制子行程的執行順序:
import multiprocessing
import os
def target_func():
print('子行程運行')
print('子行程 pid:', os.getpid())
print('子行程的 ppid:', os.getppid())
p = multiprocessing.Process(target=target_func)
p.start()
p.join()
print('主行程運行')
print('主行程 pid:', os.getpid())
上述代碼中新增了 p.join(),相應修改原先的 process.py 檔案,再來執行下:
? ~ python3 process.py
子行程運行
子行程 pid: 13386
子行程的 ppid: 13385
主行程運行
主行程 pid: 13385
可以看到,使用 p.join() 后主行程將等待子行程執行完成,然后再向下執行代碼,
2、執行緒操作
每一個行程都默認有一個執行緒,這個執行緒被稱為主執行緒,我們可以在主執行緒中創建其它執行緒來協助處理任務,這些執行緒也是并行運行的,
執行緒是行程的執行單元,CPU 調度行程時,實際上是在行程的執行緒間作切換,另外執行緒間共享它們所在行程的記憶體空間(堆疊除外),
可以使用 threading.Thread() 方法來創建執行緒:
import threading
t = threading.Thread(target=目標函式, args=(目標函式的引數,))
用 start() 方法來啟動一個執行緒:
t.start()
來看個例子:
import threading
def target_func(n):
for i in range(n):
print(i)
t = threading.Thread(target=target_func, args=(8,))
t.start()
print('主執行緒結束')
將上述代碼拷貝至檔案 thread.py 中,執行下:
? ~ python3 thread.py
0
1
主執行緒結束
2
3
4
5
6
7
上述子執行緒和主執行緒交替執行,可以使用 join() 讓主執行緒等待子執行緒執行完成:
import threading
def target_func(n):
for i in range(n):
print(i)
t = threading.Thread(target=target_func, args=(8,))
t.start()
t.join()
print('主執行緒結束')
上述代碼中新增了 t.join(),相應修改原先的 thread.py 檔案,再來執行下:
? ~ python3 thread.py
0
1
2
3
4
5
6
7
主執行緒結束
(1)執行緒鎖
多個執行緒間回共享行程的記憶體空間,如果多個執行緒同時修改和訪問同一個物件,則可能會出現非預期的錯誤,
比如下面這個例子中,我們創建了兩個執行緒,這兩個執行緒分別對 number 變數做一百萬次 +1 操作,
import threading
number = 0
def add():
for i in range(1000000):
global number
number += 1
t_1 = threading.Thread(target=add)
t_2 = threading.Thread(target=add)
t_1.start()
t_2.start()
t_1.join()
t_2.join()
print(number)
number 的預期結果應該是 2000000(兩百萬),
將上述代碼保存至檔案 thread_add.py 中,來看下實際運行結果:
? ~ python3 thread_add.py
1584627
? ~ python3 thread_add.py
1413399
? ~ python3 thread_add.py
1541521
可以看到,每次運行的結果并不一致,并且均小于 2000000,
這是因為,number += 1 其實是兩個操作——首先獲取 number,然后對獲取到的值 +1,這兩個操作并不是原子的(也就是說,這兩個操作并不一定會被 CPU 連續執行,執行第一個操作時,CPU 有可能被中斷去執行其它任務,之后又回到這里執行第二個操作),這個例子中有一種可能情形是,執行到某一時刻時,第一個執行緒獲取到 number 值為 100,緊接著第二次執行緒也獲取到 number 值為 100,第一個執行緒在 100 的基礎上 +1 并將 101 賦值給 number,第二執行緒也在 100 的基礎上 +1 并將 101 賦值給 number,由于兩個執行緒是并行運行的,它們彼此間并不知情,這樣就浪費了一次 +1 操作,最終的 number 結果也會變小,
在這種情況下想要得到正確的結果,應該對 number += 1 操作加鎖,如下:
import threading
number = 0
lock = threading.Lock()
def add():
for i in range(1000000):
global number
lock.acquire()
number += 1
lock.release()
t_1 = threading.Thread(target=add)
t_2 = threading.Thread(target=add)
t_1.start()
t_2.start()
t_1.join()
t_2.join()
print(number)
更新 thread_add.py 檔案,來看下運行結果:
? ~ python3 thread_add.py
2000000
? ~ python3 thread_add.py
2000000
? ~ python3 thread_add.py
2000000
可以看到,這次結果完全正確,但同時我們也能感受到,程式的執行速度變慢了,是的,鎖會帶來性能上的損耗,這就需要我們在正確性和性能間做取舍了,
OK,關于常見的Python高階IO操作及行程執行緒操作就和大家先分享這些,大家有疑問或者補充的話,歡迎在評論區留言!
持續為大家分享更多優質干貨中...感興趣的小伙伴記得關注一起學習呀!
灰小猿陪你一起進步!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/281273.html
標籤:python

