python學習筆記 第四章 檔案操作
阿新 • • 發佈:2020-11-02
第四章 檔案操作
4.1記憶體相關
-
示例一
v1=[11,22,33] v2=[11,22,33] #會重新開闢記憶體空間,儲存當前的值 v1=666 v2=666 #會重新開闢記憶體空間,儲存當前的值 v1="abc" v2="abc" #會重新開闢記憶體空間,儲存當前的值
-
示例二:
v1=[11,22,33,44] v1=[11,22,33] #會重新開闢記憶體空間,儲存當前的值
-
示例三:賦值操作不會開闢新的記憶體空間
#內部修改 v1=[1,2] v2=v1 #將v2的記憶體地址指向v1所指向的記憶體地址,即v1和v2指向同一個記憶體空間 v1.append(3) #修改V1的值, v2也會變化 print(v2) #[1,2,3] v2.append(4) #修改v2的值,v1也會改變 print(v1) #[1,2,3,4] #賦值操作 v1=[11,22,33,44] #開闢一個新的記憶體空間,將v1重新指向他、 #對於v2而言,其仍指向[1,2,3,4]的記憶體空間 print(v1,v2) #[11, 22, 33, 44] [1, 2, 3, 4]
#對於字串來說,其不可進行內部修改 v1="apple" v2=v1 #賦值 v1="pear" print(v1,v2) #pear,apple
-
示例四:巢狀的
v=[1,2,3] values=[11,22,v] print(values) #[11, 22, [1, 2, 3]] v.append("apple") #對v進行操作,則values因為也包含v,則其也會發生變化 print(values) #[11, 22, [1, 2, 3, 'apple']] values[-1].append(666) print(v) #[1, 2, 3, 'apple', 666] v=9999 #values指向的還是v之前的記憶體空間 print(values) #[11, 22, [1, 2, 3, 'apple', 666]] values[-1]=666 #v指向的記憶體空間還是9999 print(v) #9999
-
示例五
v1=[1,2,3] v2=["a","b","c"] v3=["x","y","z",v1,v2,v1] v3[-1].append(666) #修改了v1所指向的記憶體單元的值 print(v1) #[1, 2, 3, 666] #v3裡面也有指向v1記憶體單元的值,故v3裡面的v1值也全部會變 print(v3) #['x', 'y', 'z', [1, 2, 3, 666], ['a', 'b', 'c'], [1, 2, 3, 66]] #實際上v3裡面存的是v1和v2的記憶體地址 v3[3]=999 print(v3) #['x', 'y', 'z', 999, ['a', 'b', 'c'], [1, 2, 3, 666]] print(v1) #[1, 2, 3, 666]
記憶體垃圾:當此記憶體沒有指標指向它的時候,就會被系統當成垃圾
-
檢視記憶體地址id
v1=[1,2,3] v2=[1,2,3] v3=v1 print(id(v1),id(v2),id(v3)) #1653666177608 1653666177672 1653666177608 #v1和v3的記憶體地址是一樣的,v2的記憶體地址是新開闢的 v1=999 print(id(v1),id(v3)) #v1的記憶體地址變化了,v3的記憶體地址保持不變
-
python中為了記憶體優化,做了快取,提高效能,把一些相同的整型和字串會放到一個記憶體地址中
-
整型:(-5到256)
-
字串:除了形如"a_*" * 3
當然只有一些整型、字串會出現這樣的情況
-
-
==與is的區別
#==是判斷數值是否相等,is是判斷記憶體地址是否相等 v3=[1,2,3] v4=[2,3,4] print(v3==v4) #False print(v3 is v4) #False v1=[1,2] v2=[1,2] #v1和v2的記憶體地址並不相等 print(v1==v2) #True print(v1 is v2) #False v5=[1,2,3] v6=v5 #v6和 v5的記憶體地址相等,則值也相等 print(v5==v6) #True print(v5 is v6) #True #python快取機制導致的特殊情況 v7=10 v8=10 #由於python的快取機制,導致v7和v8實際上是一個記憶體地址 print(v7==v8) #True print(v7 is v8) #True
-
方法的記憶體操作
print("列表的append()方法") v1=[1,2,3] v2=v1 v1.append("apple") #列表是可以修改的,append()不會重新開闢一個記憶體空間 print(v1,v2) #[1, 2, 3, 'apple'] [1, 2, 3, 'apple'] print("字串的大小寫轉換如upper()") v3="apple" v4=v3 v3.upper() #由於是str型別,並不可變,會重新開闢一塊記憶體空間 v5=v3.upper() # 若有接收的值,接收者能變大寫 print(v3,v4,v5) #apple apple APPLE print("字串型別的切片") v6="apple" v7=v6[0:2] #字串並不可變,所以只能新開闢一塊記憶體空間接收切片的值 print(v6,v7) #原來的值沒有修改 print("集合的add()") v8={1,2,3} v9=v8 v8.add(666) #集合是可變型別,修改了v8地址的值,v9也會修改 print(v8,v9) #{1, 2, 3, 666} {1, 2, 3, 666} print("集合的intersection()") v10={1,2,3} v11=v10 v10.intersection({1,3,5}) #需要將其結果賦值給一個新的值 print(v10,v11) #實際上沒有對v10和v11的元素的值修改 print("列表的索引") v12=[1,2,3,4,5] #列表其實存的是指向一個大 的記憶體地址 v13=v12 print("修改前:",id(v12[0])) #140714809807904 v12[0]=[6,7,8,9] #列表中的索引指向的是一個記憶體地址,記憶體地址所指的值是索引中的值 print("修改後:",id(v12[0])) #1944748080712 ,修改前與後的索引地址是不同的 #可以發現,索引指向的地址由於值發生變化,又建立一個新的記憶體空間,並重新指向該記憶體地址 print(v12,v13) #[[6, 7, 8, 9], 2, 3, 4, 5] [[6, 7, 8, 9], 2, 3, 4, 5] print("列表的巢狀") v14=[1,2] v15=[1,2,v14] v14[0]="apple" #修改v14[0]的值,由於v15[2]指向v14的地址,所以v15[2]的值會隨著v14改變 print(v14,v15) #['apple', 2] [1, 2, ['apple', 2]] v15的值也會隨著發生變化 v15[2][1]=666 #修改的是v15所指向的v14的[1]號地址,v14和v15會隨著一起發生變化 print(v14,v15) #['apple', 666] [1, 2, ['apple', 666]] v15[2]=123 #修改的是v15[2]的值,v15[2]會重新建立一個記憶體空間,儲存記憶體地址 print(v14,v15) #['apple', 2] [1, 2, 123] v14的值不會發生變化 #列表/集合/字典 的記憶體 操作類似 print("取字典key,修改values") v16={"k1":"v1","k2":"v2"} v17=v16 print("修改前:",id(v16["k1"])) #2358676809632 v16["k1"]="apple" #v17的地址指向v16,修改 v16則v17的值也會隨之改變 print("修改後:",id(v16["k1"])) #2358676810864 print(v16,v17) #{'k1': 'apple', 'k2': 'v2'} {'k1': 'apple', 'k2': 'v2'} #再來一道列表的巢狀的題 print("列表的巢狀") v18=[1,2,3] v19=[v18,v18,v18] v19[0]="apple" #只是重新開闢一塊記憶體空間,將v19[0]指向該記憶體地址 print(v18,v19) #[1, 2, 3] ['apple', [1, 2, 3], [1, 2, 3]] v18[0]=666 print(v18,v19) #[666, 2, 3] ['apple', [666, 2, 3], [666, 2, 3]] v19[2][2]=999 print(v18,v19) #[666, 2, 999] ['apple', [666, 2, 999], [666, 2, 999]] print("字典迴圈的記憶體相關的操作") v20={} for i in range(10): v20["user"]=i print(v20) #{'user': 9} print("列表中巢狀字典的迴圈") v21=[] v22={} for i in range(10): v22["user"]=i v21.append(v22) print(v21,v22) #[{'user': 9}, {'user': 9}, {'user': 9}, {'user': 9}, {'user': 9}, {'user': 9}, {'user': 9}, {'user': 9}, {'user': 9}, {'user': 9}] {'user': 9} v23={"k1":"v1","k1":"v2"} #字典儲存的方法是 鍵通過雜湊演算法轉換成0101的值並儲存值的地址,根據值的地址可以找到對應的記憶體,找到真正的值 print(v23) #{'k1': 'v2'} 相同鍵,只會儲存最後一個鍵與值 print("列表和在迴圈中建立字典並新增入列表") v24=[] for i in range(10): v25={} v25["user"]=i v24.append(v25) print(v24,v25) #[{'user': 0}, {'user': 1}, {'user': 2}, {'user': 3}, {'user': 4}, {'user': 5}, {'user': 6}, {'user': 7}, {'user': 8}, {'user': 9}] {'user': 9} print("列表與迴圈append()") v26=[1,2,3,4] v27=[] for i in v26: v27.append(i) #相當於把數字的地址給到v27 print(v26,v27) #[1, 2, 3, 4] [1, 2, 3, 4] print(id(v26[0]),id(v27[0])) #140714614182944 140714614182944 記憶體地址相同 v27[0]=666 print(v26,v27) #[1, 2, 3, 4] [666, 2, 3, 4] 只修改v27的值,v26不會發生改變 print("列表與迴圈append(str())") v28=[1,2,3,4] v29=[] for i in v28: v29.append(str(i)) #每次生成一個新的字串新增到v29 print(v28,v29) #[1, 2, 3, 4] ['1', '2', '3', '4'] print(id(v28[0]),id(v29[0])) #140714809807904 2747158554528 記憶體地址不相同 print("帶有字串的列表與迴圈append()") v30=["apple","pear","cherry"] v31=[] for i in v30: v31.append(i.upper()) print(v30,v31) #['apple', 'pear', 'cherry'] ['APPLE', 'PEAR', 'CHERRY'] print(id(v30[0]),id(v31[0])) #2084019629952 2084019628048 記憶體地址不一樣 v32=[] for i in v30: v32.append(i+"sb") #括號中的值修改過之後就會生成新的地址,沒有修改就還是原來的地址 print(v30,v32) #['apple', 'pear', 'cherry'] ['applesb', 'pearsb', 'cherrysb'] print(id(v30[0]),id(v32[0])) #1931911098296 1931911457792 記憶體地址也不同
4.2深淺拷貝
4.2.1淺拷貝
拷貝第一層
4.2.2深拷貝
拷貝所有的資料(可變)
import copy
#在字串、整型、布林型別、元組的拷貝中,由於這些是不可變的資料型別,深淺拷貝並沒有區別,都會開闢記憶體空間拷貝一份資料,拷貝出的資料會被copy()所賦予的值指向(究竟有沒有建立新的空間?不知道,但是由於小資料池的緣故,第一層不可變資料型別拷貝出來的記憶體地址是相同的)
v1="apple"
v2=copy.copy(v1) #淺拷貝 由於小資料池的緣故,其記憶體地址相同(應該不一樣的)
print(id(v1),id(v2)) #2015823325256 2015823325256
v3=copy.deepcopy(v1) #深拷貝 由於小資料池的緣故,其記憶體地址相同(應該不一樣的)
print(id(v1),id(v3)) #2015823325256 2015823325256
#淺拷貝:只拷貝第一層
#深拷貝:拷貝巢狀層次中的所有可變型別(拷貝所有的資料(可變))
v4=[1,2,3,[11,22,33]]
v5=copy.copy(v4)
v6=copy.deepcopy(v4)
print(id(v4),id(v5),id(v6)) #1895510213704 1895510273032 1895510270216
print(id(v4[0]),id(v5[0]),id(v6[0])) #140725113574432 140725113574432 140725113574432
print(id(v4[-1]),id(v5[-1]),id(v6[-1])) #1895510213320 1895510213320 1895510373896
#可以發現深淺拷貝的記憶體地址都是新的,這是由於深淺拷貝都會拷貝一個一樣大小的新的列表(第一層),前面三個資料是不可變型別,
#就將其記憶體地址存放到新的列表(第一層)中,也就是通過新列表(第一層)的記憶體地址可以索引到原來前面的三個資料
#但是最後一個數據是可變的列表,淺拷貝就將列表[11,22,33]的記憶體地址儲存到新的列表(第一層)中,
#深拷貝則會再建立一個新的列表(第二層)並存放11,22,33的地址,通過新列表(第二層)的地址索引到原來的資料
#同理對於字典、集合這些可變的資料型別也是如此
#注意深拷貝和淺拷貝拷貝出來的資料都是一樣的,只是地址不一樣
#特殊情況
v7=(1,2,[666.999],4)
v8=copy.copy(v7)
v9=copy.deepcopy(v7)
print(id(v7),id(v8),id(v9)) #1701626030824 1701626030824 1701626031064
#發現元組中嵌套了可變的資料型別,導致深拷貝時也會重新建立一個元組,所以元組的地址發生了變化
4.2.3修改深淺拷貝中的檔案
還是以上述程式碼的v1、v2、v3作為例子
當修改深淺拷貝的資料的時候,如需要修改v2[0]=666,為了儲存666,會重新開闢一塊記憶體空間儲存666的值,並使原來的資料地址會指向新開闢的記憶體空間的地址,這樣就保證了修改拷貝的檔案中的值不會改變到原來的v1。
4.3檔案的基本操作
obj=open("路徑",mode="模式",encoding="編碼")
obj.write(x)
y=obj.read()
obj.close()
4.4開啟模式
- 只讀只寫字串 r / w / a
- 可讀可寫字串 r+ / w+ / a+
- 只讀只寫二進位制 rb / wb /ab
- 可讀可寫二進位制 r+b / w+b / a+b
4.5讀寫操作
-
read(),全部讀到記憶體
-
read(1)
-
1表示一個字元
obj=open("a.txt",mode="r",encoding="utf-8") data=obj.read(1) #讀取一個字元 obj.close() print(data)
-
1表示一個位元組
obj=open("a.txt",mode="rb") data=obj.read(1) obj.close() print(data)
-
-
readlines() 逐行進行讀取
-
readline() 讀取游標後一行
-
for迴圈 可以用在超級大檔案,使得檔案不會一次性讀取到記憶體中
-
write(字串)
obj=open("a.txt",mode="w",encoding="utf-8") data=obj.wirte("我要寫入檔案拉") obj.close()
-
write(二進位制)
obj=open("a.txt",mode="wb") data=obj.wirte("我想用二進位制寫檔案".encode("utf-8")) obj.close()
4.6檔案的中級操作
-
調整游標的位置seek() 以位元組為單位(無論mode是否為二進位制)
-
獲取游標的當前所在位元組的位置tell()
可以通過游標的操作對於大檔案的下載中斷以後繼續進行。
obj=open("aaa.txt",mode="r",encoding="utf-8") data=obj.tell() obj.close()
-
flush() 強制將記憶體中的資料寫入到硬碟上
-
詳細的關於檔案中級操作的解釋
#檔案操作
#開啟和關閉檔案 open() close()
#方式一:先開啟檔案,一頓操作以後,再關閉檔案
"""
file_obj=open("test.txt",mode="r",encoding="utf-8")
content=file_obj.read()
print(content)
file_obj.close() #關閉的時候才會把檔案強制刷到硬碟上面
"""
#方式二:為了防止自己忘記關閉檔案,導致檔案不能從記憶體寫到硬碟上,用with...as...加縮排進行檔案操作
"""
with open("test.txt",mode="r",encoding="utf-8") as file_obj:
data=file_obj.read()
#縮排中的程式碼執行以後,自動關閉檔案
"""
#檔案的兩種操作:read() write() 讀和寫
#read()讀取全部的內容 / read(1) 從游標處開始往後讀取一個字元 / readlines() 逐行進行讀取,得到的是列表的形式
#readline() 僅讀取游標後一行 / for i in file_obj: 也可以逐行進行讀取,並且不會一次性放入到記憶體中,但是會有換行符,需要用strip()去除
#補充strip()可以去除空格、\n、\t等
#write() 將要寫入的內容填入括號中即可,而且可以按照write("你好胖\n")會進行換行。會根據游標的位置往後寫(需要注意有例外,如a/a+/ab)
"""
file_obj=open("aaa.txt",mode="r",encoding=("utf-8"))
content=file_obj.readlines()
print(content) #['今朝有酒今朝醉\n', '遊湖\n', '又用\n', '與i及']
file_obj.seek(0)
content1=file_obj.readline() #直接讀取檔案的後一行
print(content1) #今朝有酒今朝醉
print("************************")
file_obj.seek(0) #再次把游標移動到首部
#如果需要讀取一個非常大的檔案,需要分開將檔案放入記憶體中,不至於將一個大檔案直接放入到記憶體當中
for i in file_obj:
result=i.strip() #在此處的主要功能就是去除\n
print(result)
file_obj.close()
"""
#三種基本的檔案操作mode r/w/a 的區別:
#r 是隻能讀取檔案,從游標處讀取內容,不能寫,且游標預設起始位置位於0(若沒有該檔案,則會報錯)
#w 只能寫檔案,不能讀取檔案,在檔案進行open()的時候就會先清空檔案,然後將新的檔案內容寫入,寫完以後游標會跑到最後(若沒有該檔案,則會新建一個檔案)
#a 追加寫入,不能讀,游標預設在末尾,在此mode的情況下,只能追加到末尾,無論游標移動到哪裡(若沒有該檔案,則會建立一個新的檔案)
#若是不能讀或寫的mode下面,進行讀或寫的操作會報錯:io.UnsupportedOperation: not writable
"""
file_obj=open("aaa.txt",mode="a",encoding="utf-8")
file_obj.write("你好")
file_obj.close() #會建立一個新的檔案
"""
#三種基本的檔案操作mode+ r+/w+/a+
#r+ 可讀可寫,從游標位置開始讀寫,預設游標位置為0(檔案的開頭處)讀或者寫,寫的時候由於游標位置為0,所以會覆蓋後面的內容
"""
file_obj=open("aaa.txt",mode="r+",encoding="utf-8")
file_obj.seek(3)
content=file_obj.read() #預設從游標的位置開始讀,當然寫也是如此
print(content)
file_obj.close()
"""
#seek()移動游標的操作 表示把游標移動到第幾個位元組的位置,不寫預設游標在所在的mode情況
"""
file_obj=open("aaa.txt",mode="r+",encoding="utf-8")
file_obj.seek(3)
file_obj.write("呵呵") #如果想在末尾新增,可以先用read()就可以將游標移動到末尾,再進行寫的操作
file_obj.close()
"""
#當不知道游標處於何處時
#w+ 可讀可寫,同樣進行檔案操作的時候會把檔案清空
#注意:先寫後讀,發現讀不到資料,這是由於寫完後游標已經移動到末尾,所以讀的時候仍按游標的位置開始讀就讀不到東西
"""
file_obj=open("aaa.txt",mode="w+",encoding="utf-8") #此時檔案被清空
file_obj.write("今朝有酒今朝醉") #寫完以後游標的位置當然是在末尾
content=file_obj.read() #從游標開始的位置讀取,由於游標已經在末尾,讀取的東西為空
print(content+"讀不到的東西")
file_obj.seek(0) ##所以可以先將游標移動到開始的位置,就可以讀取原來的檔案的內容
result=file_obj.read() #在記憶體中已經 有新的內容寫入,游標從頭開始讀取,就可以得到記憶體中新寫入的內容
print(result) #今朝有酒今朝醉
file_obj.close()
"""
#a+ 可讀可寫,預設游標正在最後,所以想要讀取資料,需要將游標移動到起始位置。當然,即使游標移動到最前,也還是寫到末尾
"""
file_obj=open("aaa.txt",mode="a+",encoding="utf-8")
file_obj.write("我要寫東西") #由於a+對於檔案寫操作都是追加到末尾,所以最終檔案的末尾會加上“我要寫東西”的字串
content=file_obj.read() #可以讀取檔案
print(content)
file_obj.close()
"""
#注意:對於檔案操作來說,由於檔案會讀取到記憶體中,檔案一般不存在修改
#實在需要修改,其實是在記憶體中修改,再重新寫入到硬碟中,不是直接對檔案進行操作。
對於一些大的檔案來說,想要修改其中的內容,但是由於檔案過大不能一次性讀入到記憶體,可以用:
f1=open("aaa,txt",mode="r",encoding="utf-8")
f2=open("aaa.txt",mode="w",encoding="utf-8")
for line in f1:
new_line=line.replace("帥哥","醜逼")
f2.write(new_line)
f1.close()
f2.close()
#當然也可以採用with open(...) as ...: 縮排的方式直接將兩個檔案的開啟
with open("a.txt",mode="r",encoding="utf-8") as f1,open ("a",mode="w",encoding="utf-8") as f2:
for line in f1:
new_line=line.replace("美女","女神")
f2.write(new_line)
4.7一些檔案操作的練習題
#一些關於檔案操作的練習題
#練習一:將user中的元素根據_連結,並寫入aaa.txt檔案中
#user=["apple","pen"]
"""
user=["apple","pear"]
data="_".join(user) #用到字串中的連結
print(data)
file=open("aaa.txt",mode="w",encoding=("utf-8"))
file.write(data)
file.close()
"""
#練習二:將uesr中的元素根據_連結,並寫入aaa.txt的檔案中
#user=[{"name":"apple","pwd":"123"},{"name":"pen","pwd":"123"}
"""
user=[{"name":"apple","pwd":"123"},{"name":"pen","pwd":"123"}]
file=open("aaa.txt",mode="w",encoding="utf-8")
for i in user:
#i其實是以字典的形式出現的
result="_".join(i.values()) #也可以用key來取values
#line="%s%s"%(i["name"],i["pwd"])
file.write(result+"\n")
file.close()
"""
#練習三:將aaa.txt檔案中的檔案讀取出來,並新增到一個列表user中
"""
#方式一:
user=[]
file=open("aaa.txt",mode="r",encoding="utf-8")
data=file.read()
user.append(data)
file.close()
print(user) #['apple_123\npen_123\n']
#字串其實可以通過\n來分割一下,這裡需要 用到split() ,這個也是字串的方法
#data=data.strip() #['apple_123', 'pen_123']
result=data.split("\n")
print(result) #['apple_123', 'pen_123', '']這樣就可以拿到列表
#但是發現後面還有""空字串,所以可以用strip()去除
print("************華麗的分割線************")
#方式二:
user1=[]
file=open("aaa.txt",mode="r",encoding="utf-8")
for i in file:
print(i) #逐行進行列印,而且保留有換行符
data1=i.strip()
user1.append(data1)
file.close()
print(user1) #最終也能得到['apple_123', 'pen_123']
"""
4.8理解檔案操作的本質
理解了檔案操作和進位制以後,需要理解檔案操作的本質
#理解檔案操作的本質
"""
#根據字串的內容以及encoding所指示的編碼轉化為二進位制,寫入到檔案中
file=open("aaa.txt",mode="w",encoding="utf-8")
file.write("今朝有酒今朝醉")
file.close()
"""
"""
#讀取硬碟上的二進位制進記憶體,將二進位制按照encoding的編碼,轉換成字串
file=open("aaa.txt",mode="r",encoding="utf-8")
data=file.read()
file.close()
"""
#rb 直接讀取二進位制
"""
file=open("aaa.txt",mode="rb")
show=file.read()
print(show) #b'\xe4\xbd\xa0\xe5\xa5\xbd\xe6\xa3\x92'
#可以發現讀取的是2進位制,表現出來的是16進位制(可以發現都帶\x,這代表的就是16進位制,這是為了方便表示二進位制)
file.close()
"""
#wb 直接寫入二進位制
"""
file=open("aaa.txt",mode="wb")
#file.write("你好") #TypeError: a bytes-like object is required, not 'str'
#報錯,顯示不能直接寫入字串,只能以二進位制的形式寫入
#所以最好先將要寫入的字元轉換成二進位制encode(),再將二進位制寫入檔案中
data="你好棒"
content=data.encode("utf-8") #以utf-8的編碼形式,將字串轉換成二進位制
file.write(content) #寫入二進位制
file.close()
"""
#ab 直接追加二進位制
#一般對於圖片/音訊/視訊/未知編碼,常用rb/wb/ab
字串轉二進位制 encode()
二進位制轉字串 decode()
4.9檔案的修改
with open("aaa.txt",mode="r",encoding="utf-8") as f1:
data=f1.read()
#只能將檔案讀取到記憶體中修改
new_data=data.replace("酒","牛奶")
#將檔案從記憶體重新寫到硬碟中
with open("aaa.txt",mode="w",encoding="utf-8") as f1:
f1.write(new_data)