跟我一起學(xué)Python(八)

一、IO編程

讀寫文件是最常見的IO操作,Python內(nèi)置了讀寫文件的函數(shù)。
文件讀寫的原理:在磁盤上讀寫文件的功能都是由操作系統(tǒng)提供的,現(xiàn)代操作系統(tǒng)不允許普通的程序直接操作磁盤,所以,讀寫文件就是請求操作系統(tǒng)打開一個文件對象(通常稱為文件描述符),然后,通過操作系統(tǒng)提供的接口從這個文件對象中讀取數(shù)據(jù)(讀文件),或者把數(shù)據(jù)寫入這個文件對象(寫文件)。

讀文件

#如果文件不存在,open()函數(shù)就會拋出一個IOError的錯誤,
>>> f = open('/Users/Administrator/angular-cli.json',"r")#絕對地址  當(dāng)前的盤符+'/Users/Administrator/angular-cli.json' 構(gòu)成絕對地址
>>> f.read()
'{\n  "warnings": {\n    "packageDeprecation": false\n  }\n}\n'

最后一步是調(diào)用close()方法關(guān)閉文件。文件使用完畢后必須關(guān)閉,因為文件對象會占用操作系統(tǒng)的資源,并且操作系統(tǒng)同一時間能打開的文件數(shù)量也是有限的:

>>> f.close()

由于文件讀寫時都有可能產(chǎn)生IOError,一旦出錯,后面的f.close()就不會調(diào)用。所以,為了保證無論是否出錯都能正確地關(guān)閉文件,我們可以使用try ... finally來實現(xiàn):

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

此時我們先來學(xué)一下python里面的try finally 語句

def f():
    try:
        print 1
        return 1
    finally:
        print 0
        return 0
s = f()
print(s)

輸出為:1 0 0

不論try里執(zhí)行什么,都會執(zhí)行到finnally語句,且如果finally里面有return語句,就會替代了try里面的return語句。

def f():
    try:
        print 1
        return 1
    finally:
        print 0
        #return 0
s = f()
print(s)

輸出為 1 0 1

def f():
    try:
        print 1
        return 1
    except:
        return 2
    else:
        print 3
        return 3
    finally:
        print 0
        return 0
s = f()
print(s)

輸出為:1 0 0

只要try語句里有return語句(不包括finally語句里面的return語句),則不執(zhí)行else語句。
只要try語句執(zhí)行后拋出錯誤,緊跟著就會執(zhí)行except語句。

利用try...finally語句太繁瑣了,Python引入了with語句來自動幫我們調(diào)用close()方法:

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

調(diào)用read()會一次性讀取文件的全部內(nèi)容,如果文件有10G,內(nèi)存就爆了,所以,要保險起見,可以反復(fù)調(diào)用read(size)方法,每次最多讀取size個字節(jié)的內(nèi)容。外,調(diào)用readline()可以每次讀取一行內(nèi)容,調(diào)用readlines()一次讀取所有內(nèi)容并按行返回list。因此,要根據(jù)需要決定怎么調(diào)用。

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

Python strip() 方法用于移除字符串頭尾指定的字符(默認為空格或換行符)或字符序列。

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,常用作臨時緩沖。

二進制文件

前面講的默認都是讀取文本文件,并且是UTF-8編碼的文本文件。要讀取二進制文件,比如圖片、視頻等等,用'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')

寫文件

寫文件和讀文件是一樣的,唯一區(qū)別是調(diào)用open()函數(shù)時,傳入標(biāo)識符'w'或者'wb'表示寫文本文件或?qū)懚M制文件:

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

with寫法

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

寫文件時,如果文件事先不存在,則會創(chuàng)建出來,且只有在close()操作結(jié)束之后才可以顯示出寫進去的內(nèi)容。
因為當(dāng)我們寫文件時,操作系統(tǒng)往往不會立刻把數(shù)據(jù)寫入磁盤,而是放到內(nèi)存緩存起來,空閑的時候再慢慢寫入。只有調(diào)用close()方法時,操作系統(tǒng)才保證把沒有寫入的數(shù)據(jù)全部寫入磁盤。

關(guān)于open()的mode參數(shù):
'r':讀
'w':寫
'a':追加
'r+' == r+w(可讀可寫,文件若不存在就報錯(IOError))
'w+' == w+r(可讀可寫,文件若不存在就創(chuàng)建)
'a+' ==a+r(可追加可寫,文件若不存在就創(chuàng)建)
對應(yīng)的,如果是二進制文件,就都加一個b就好啦:
'rb'  'wb'  'ab'  'rb+'  'wb+'  'ab+'

二、StringIO和BytestIO

很多時候,數(shù)據(jù)讀寫不一定是文件,也可以在內(nèi)存中讀寫。
StringIO顧名思義就是在內(nèi)存中讀寫str。

>>> 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!')
s = f.read()
print(s)

注意:初始化為空,用write方法寫入數(shù)據(jù)的StringIO對象,無法像讀文件一樣讀取

BytesIO

StringIO操作的只能是str,如果要操作二進制數(shù)據(jù),就需要使用BytesIO。

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

三、操作文件和目錄

如果我們要操作文件、目錄,可以在命令行下面輸入操作系統(tǒng)提供的各種命令來完成。比如dir、cp等命令,而Python內(nèi)置的os模塊也可以直接調(diào)用操作系統(tǒng)提供的接口函數(shù)。

>>> import os
>>> os.name # 操作系統(tǒng)類型
'posix'

如果是posix,說明系統(tǒng)是Linux、Unix或Mac OS X,如果是nt,就是Windows系統(tǒng)。
要獲取詳細的系統(tǒng)信息,可以調(diào)用 uname()函數(shù)

環(huán)境變量

在操作系統(tǒng)中定義的環(huán)境變量,全部保存在os.environ這個變量中,可以直接查看:
要獲取某個環(huán)境變量的值,可以調(diào)用os.environ.get('key')

操作文件和目錄

操作文件和目錄的函數(shù)一部分放在os模塊中,一部分放在os.path模塊中,

# 查看當(dāng)前目錄的絕對路徑:
>>> os.path.abspath('.')
'/Users/michael'
# 在某個目錄下創(chuàng)建一個新目錄,首先把新目錄的完整路徑表示出來:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后創(chuàng)建一個目錄:
>>> os.mkdir('/Users/michael/testdir')
# 刪掉一個目錄:
>>> os.rmdir('/Users/michael/testdir')

把兩個路徑合成一個時,不要直接拼字符串,而要通過os.path.join()函數(shù),
在Linux/Unix/Mac下,

part-1/part-2

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

part-1\part-2

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

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

這些合并、拆分路徑的函數(shù)并不要求目錄和文件要真實存在,它們只對字符串進行操作。

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

列出當(dāng)前目錄下的所有目錄

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

os.listdir() 方法用于返回指定的文件夾包含的文件或文件夾的名字的列表。這個列表以字母順序。 它不包括 '.' 和'..' 即使它在文件夾中。

只支持在 Unix, Windows 下使用。
列出所有的.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']

os模塊下的常用方法
os.path.isdir()用于判斷對象是否為一個目錄
os.path.isfile()用于判斷對象是否為一個文件
os.getcwd() 獲取當(dāng)前所在目錄
os.path.abspath(path) 返回path規(guī)范化的絕對路徑

>>> os.path.abspath("test.txt") 
'C:\\WINDOWS\\system32\\test.txt'

os.path.split(path) 將path分割成目錄和文件名二元組返回。

>>> os.path.split('C:\\WINDOWS\\system32\\test.txt')
('C:\\WINDOWS\\system32', 'test.txt')
>>>

os.path.splitext(x) 將文件名和后綴名分割二元數(shù)組返回

>>> os.path.splitext("test.txt")
('test', '.txt')

os.path.dirname(path) 返回path的目錄。其實就是os.path.split(path)的第一個元素。
os.path.basename(path) 返回path最后的文件名。如何path以/或\結(jié)尾,那么就會返回空值。即os.path.split(path)的第二個元素。

>>> os.path.basename('C:\\WINDOWS\\system32\\test.txt')
'test.txt'

os.path.commonprefix(list) 返回list中,所有path共有的最長的路徑。
os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False。
os.path.isabs(path) 如果path是絕對路徑,返回True。
os.path.join(path1[, path2[, ...]]) 將多個路徑組合后返回,第一個絕對路徑之前的參數(shù)將被忽略
os.path.getatime(path) 返回path所指向的文件或者目錄的最后存取時間。
os.path.getmtime(path) 返回path所指向的文件或者目錄的最后修改時間

復(fù)制文件的函數(shù)是不在os模塊里面的,但是shutil模塊提供了copyfile()的函數(shù)。
shutil模塊
os.mkdir("file") 創(chuàng)建目錄
復(fù)制文件:
shutil.copyfile("oldfile","newfile") oldfile和newfile都只能是文件
shutil.copy("oldfile","newfile") oldfile只能是文件,newfile可以是文件,也可以是目標(biāo)目錄
復(fù)制文件夾:
shutil.copytree("olddir","newdir") olddir和newdir都只能是目錄,且newdir必須不存在
重命名文件(目錄)
os.rename("oldname","newname") 文件或目錄都是使用這條命令
移動文件(目錄)
shutil.move("oldpos","newpos")
刪除文件
os.remove("file")
刪除目錄
os.rmdir("dir")只能刪除空目錄
shutil.rmtree("dir") 空目錄、有內(nèi)容的目錄都可以刪
轉(zhuǎn)換目錄
os.chdir("path") 換路徑

問題:編寫一個程序,能在當(dāng)前目錄以及當(dāng)前目錄的所有子目錄下查找文件名包含指定字符串的文件,并打印出相對路徑

import os
import re
from io import StringIO
p = os.getcwd() 
list = []
def fun(t):
    global list
    list = list + [os.path.splitext(x)[0] for x in os.listdir(t) if os.path.isfile(x)]
    #print([os.path.splitext(x) for x in arr if os.path.isfile(x)])
    file_dir = [os.path.splitext(x)[0] for x in os.listdir(t) if os.path.isdir(x)]
    for file in file_dir:
        fun(t + "\\" + file )
        # print(os.listdir(t + "\\" + file))
fun(p)
print(list)
for item in list:
    if(re.search('n', item)):
        print(item)

打印相對路徑 : print(__ file __)

四、序列化

pickle

我們把變量從內(nèi)存中變成可存儲或傳輸?shù)倪^程稱之為序列化,在Python中叫pickling,在其他語言中也被稱之為serialization,marshalling,flattening等等,都是一個意思。

序列化之后,就可以把序列化后的內(nèi)容寫入磁盤,或者通過網(wǎng)絡(luò)傳輸?shù)絼e的機器上。
反過來,把變量內(nèi)容從序列化的對象重新讀到內(nèi)存里稱之為反序列化,即unpickling。

Python提供了pickle模塊來實現(xiàn)序列化。
首先,我們嘗試把一個對象序列化并寫入文件:

import pickle
d = dict(name='Bob', age=20, score=88)
f = open('dump.txt', 'wb')
pickle.dump(d, f)
f.close()

此時已經(jīng)d寫入了當(dāng)前目目下dump.txt的文件里,科室當(dāng)你打開文件時,發(fā)現(xiàn)里面是亂碼,根本看不懂。這些都是Python用自己的方式保存的對象內(nèi)部信息。
當(dāng)我們要把對象從磁盤讀到內(nèi)存時,可以先把內(nèi)容讀到一個bytes,然后用pickle.loads()方法反序列化出對象,也可以直接用pickle.load()方法從一個file-like Object中直接反序列化出對象。

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

Pickle的問題和所有其他編程語言特有的序列化問題一樣,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的數(shù)據(jù),不能成功地反序列化也沒關(guān)系。

JSON

如果我們要在不同的編程語言之間傳遞對象,就必須把對象序列化為標(biāo)準(zhǔn)格式,比如XML,但更好的方法是序列化為JSON,因為JSON表示出來就是一個字符串,可以被所有語言讀取,也可以方便地存儲到磁盤或者通過網(wǎng)絡(luò)傳輸。JSON不僅是標(biāo)準(zhǔn)格式,并且比XML更快,而且可以直接在Web頁面中讀取,非常方便。

Python內(nèi)置的json模塊:如下即可把Python對象轉(zhuǎn)化成JSON

>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'

類似的也可以直接把JSON寫入file-like里

import json
d = dict(name='Bob', age=20, score=88)
with open('dump.txt',"w") as f:
    json.dump(d,f)
f.close()

load:把文件打開,并把字符串變換為數(shù)據(jù)類型

import json
with open('dump.txt','r') as load_f:
    load_dict = json.load(load_f)
    print(load_dict)

loads: 將 字符串 轉(zhuǎn)換為 字典

import json
d = dict(name='Bob', age=20, score=88)
json_str = json.dumps(d)    #dumps:將python中的 字典 轉(zhuǎn)換為 字符串
new_dict = json.loads(json_str)
print(new_dict)

class對象的序列化
按照dict的轉(zhuǎn)化方法是不可以的,因為dumps()方法不知道如何將Student實例變?yōu)橐粋€JSON的{}對象。所以此時要給
dumps()方法新增一個參數(shù)default,可選參數(shù)default就是把任意一個對象變成一個可序列為JSON的對象,我們只需要為Student專門寫一個轉(zhuǎn)換函數(shù),再把函數(shù)傳進去即可:

import json
def student2dict(std):
    return {
        'name': std.name,
        'age': std.age,
        'score': std.score
    }
class Student(object):
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

s = Student('Bob', 20, 88)
print(json.dumps(s, default=student2dict))

但是如果你有好多class類都需要被轉(zhuǎn)換,那么這種發(fā)放就會太繁瑣
通常class類實例都有一個__dict__屬性,**.__dict__就得到了該 類對應(yīng)的dict對象,也有少數(shù)例外,比如定義了slots的class。

同樣的道理,如果我們要把JSON反序列化為一個Student對象實例,loads()方法首先轉(zhuǎn)換出一個dict對象,然后,我們傳入的object_hook函數(shù)負責(zé)把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)) #打印出的是反序列化的Student實例對象。

json.dumps的參數(shù)
sort_keys=True 是告訴編碼器按照字典排序(a到z)輸出。
indent=None 參數(shù)根據(jù)數(shù)據(jù)格式縮進顯示,讀起來更加清晰。None默認不縮進(indent=2表示縮進兩個字節(jié))
skipkeys=True,在encoding過程中,dict對象的key只可以是string對象,如果是其他類型,那么在編碼過程中就會拋出ValueError的異常。skipkeys可以跳過那些非string對象當(dāng)作key的處理.
ensure_ascii=False 輸出真正的中文默認為True,輸出的是ASCII字符

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • pyton review 學(xué)習(xí)指南 https://www.zhihu.com/question/29138020...
    孫小二wuk閱讀 1,192評論 0 2
  • 免疫力低下的時候,人體就會非常的虛弱,這時候作為人體最大器官的皮膚就會開始報警,大家常見的痘痘就是皮膚的警報器。雖...
    濟士康閱讀 7,454評論 0 0
  • 遇到輸入內(nèi)容時 。。。。。。。。。。 1.考慮未輸入輸入輸入不規(guī)范,各種狀態(tài) 2.輸入后必有地方需要展現(xiàn),切勿忘記...
    一尺厚閱讀 226評論 0 0

友情鏈接更多精彩內(nèi)容