File I/O

I/O = Input/Output

名詞介紹

  • Directory 目錄
    其實就是資料夾,不過稍微廣義一點,像是我們很少把根目錄叫做資料夾

  • Flie 檔案
    檔案大致上可以分兩種,文字檔 (text file) 跟二進位檔 (binary file)
    文字檔你可以用編輯器直接開起來,你可以看懂他,像是 txt, html


    二進位檔必須藉由解碼器才能開,用文字編輯器硬開你也看不懂,像 zip, png, docx

  • Filename Extension 副檔名
    副檔名是作業系統用來標示檔案格式的一種機制,檔名中最後一個句點後即為該檔案的副檔名
    副檔名重要的作用是讓系統決定當使用者想打開這個檔案的時候用哪種軟體執行

  • Path 路徑
    檔案的位置稱為路徑,又可以分為相對路徑跟絕對路徑
    絕對路徑是以根目錄為基準的完整路徑,不會因工作目錄不同而產生變化
    相對路徑以當前工作目錄為基準,使檔案引入時能在已知範圍搜尋,整個專案資料移動時比較不需要更改路徑
    參考:各作業系統與shell中路徑的表示法
    通常 . 代表當前目錄, .. 代表上一層目錄,目錄跟目錄或是目錄跟檔案間用 / 分隔

檔案讀寫

檔案開了讀寫完一定要關
檔案開了讀寫完一定要關
檔案開了讀寫完一定要關

開檔

# file_object = open(file, mode='r')

f = open('hello.py', 'r')

# ./hello.py
# print('hello')
# print('hi')
  • file_object: 檔案物件
    跟一般的物件(變數)一樣,可以自己取名子
    如果把 f 輸出會得到類似輸出class的物件資訊

    ​​​​print(f)
    ​​​​# output:
    ​​​​#  <_io.TextIOWrapper name='hello.py' mode='r' encoding='UTF-8'>
    
  • file: 檔案路徑
    相對或絕對路徑都可以,以字串方式輸入,要包含副檔名
    如果直接打檔案(如範例)則視為從工作目錄下尋找

  • mode: 開檔模式
    跟 Python 講你要拿這個檔案做什麼,預設是 'r''rt'

    字符 含意
    r 讀取(默認)
    w 寫入,並先截斷文件
    x 排他性創建(如文件已存在則失敗)
    a 打開文件用於寫入(如文件已存在則在最後追加)
    b 二進制模式
    t 文本模式(默認)
    + 打開用於更新(讀取與寫入)

    'r+''r+b' 會打開檔案用於讀寫並不清空內容
    'w+''w+b' 會打開檔案用於讀寫但清空內容

  • 其他 open 的參數
    參見:官方文檔
    open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
    其中比較會用到的還有 encodingnewline

讀檔

讀檔主要有兩個方法

  1. 讀一整列

    ​​​​# string = file_object.readline()
    ​​​​s1 = f.readline()
    ​​​​s2 = f.readline()
    ​​​​print(s1, end='')
    ​​​​print(s2, end='')
    ​​​​# output:
    ​​​​# print('hello')
    ​​​​# print('hi')
    
  2. 讀特定數量字元

    ​​​​# string = file_object.read(lenght)
    ​​​​s = f.read(17)
    ​​​​print(s, end='')
    ​​​​# output:
    ​​​​# print('hello')
    ​​​​# pr
    

    如果 read() 不給長度,則會一次讀進整份檔案

寫檔

  1. 記得要用可寫入的方式開檔
    ​​​​# file_object.write(string)
    ​​​​f.write('Nice to meet you.\n')
    

關檔

  1. 很重要

    ​​​​# file_object.close()
    ​​​​f.close()
    

    不關檔的壞處

    • 佔你記憶體的空間,讓你程式變慢
    • 在你關檔之前,檔案都不會被真的寫入
    • 你有可能開超過數量上限的檔案
    • 看起來很不專業
  2. 關檔前都不會寫入,如果想馬上寫入檔案怎麼辦

    ​​​​f.flush()
    

常見的寫法

  1. ​​​​# with open(file, mode) as file_object:
    ​​​​#     do something
    ​​​​
    ​​​​
    ​​​​with open('hello.py', 'r') as f:
    ​​​​    print(f.read(), end='')
    

    檔案只有在 with 的區塊裡才是開的,所以不用關檔
    上面的程式碼等同下面這種寫法

    ​​​​f = open('hello.py', 'r')
    ​​​​print(f.read(), end='')
    ​​​​f.close()
    
  2. ​​​​# with open(file, mode) as file_object:
    ​​​​#     for line in file_object:
    ​​​​#         do something
    ​​​​
    ​​​​
    ​​​​with open('hello.py', 'r') as f:
    ​​​​    for line in f:
    ​​​​        print(line, end='')
    

    :arrow_up: 遍歷所有列

  3. ​​​​# with open(file, mode) as file_object:
    ​​​​#     print(string, file=file_object)
    ​​​​
    ​​​​with open('bonjour.txt', 'w') as f:
    ​​​​    print('Bonjour, monde', file=f, flush=True)
    ​​​​    print("Je m'appelle Robert", file=f)
    

    這樣就可以直接用 print 寫入了,也可以加 flush=True 更新文件

Pickle

pickle
阿,不是這個
pickle 官方文件

pickle 是 Python 專用,用來直接存物件的東西
又因為 Python 幾乎所有東西都是以物件實作的,所以幾乎所有東西都能用 pickle 存成一個二進位檔

# pickle.dump(object, file_object)
# var_name = pickle.load(file_object)
import pickle

dic = {1: 'a', 2: 'b', 3: 'c'}
print(dic)
with open('demo', 'wb') as f:
    pickle.dump(dic, f)
with open('demo', 'rb') as f:
    dic2 = pickle.load(f)
print(dic2)

pickle 的設計就是要快速的存取 Python 的物件,包含除了一般常見的變數也包含函數、類別等

def func1(a, b): return a+b

with open('demo', 'wb') as f:
    pickle.dump(func1, f)
with open('demo', 'rb') as f:
    func2 = pickle.load(f)
print(func1(1, 2), func2(1, 2))

但由於 pickle 保留完整的 Python 物件,官方文件也提到了 pickle 的危險性,如果存取未完全信任的 pickle 可能會使其入侵你原本的程式

JSON

JSON (JavaScript Object Notation) 是一種人看得懂的存資料方法
JSON 官方
Python 官方文件

JSON 常常是不同程式之間交換資料的格式,JSON 的官方文件下面也列了一些
神奇我在成大資工特選複試的時候沒學好 C++ 的 JSON 輸出就下去了

這裡是一份 JSON 的範例,其實可以看成 Python 的 list 跟 dict 的組合
可以按這裡下載

{
    "arguments": {
        "number": 10
    },
    "url": "http://localhost:8080/restty-tester/collection",
    "method": "POST",
    "header": {
        "Content-Type": "application/json"
    },
    "body": [
        {
            "id": 0,
            "name": "name 0",
            "description": "description 0"
        },
        {
            "id": 1,
            "name": "name 1",
            "description": "description 1"
        }
    ],
    "output": "json",
    "delayed": false,
    "success": true,
    "ttl": 256.0,
    "secret": null
}
  • json.load :從檔案讀 json
  • json.loads :從字串讀 json
# var = json.load(file_object)
# var = json.loads(string)
import json

with open('sample.json', 'r') as f:
    obj = json.load(f)
    print(obj)
    
with open('sample.json', 'r') as f:
    obj = json.loads(f.read())
    print(obj)
  • json.dump :把 dict 或 list 換成 string 然後寫進檔案
  • json.dumps :把 dict 或 list 換成 string
# json.dump(dict or list, file_object)
# json.dumps(dict or list)

# a 就是 sample.json 讀出來的結果
a = {'arguments': {'number': 10}, 'url': 'http://localhost:8080/restty-tester/collection', 'method': 'POST', 'header': {'Content-Type': 'application/json'}, 'body': [{'id': 0, 'name': 'name 0', 'description': 'description 0'}, {'id': 1, 'name': 'name 1', 'description': 'description 1'}], 'output': 'json', 'delayed': False, 'success': True, 'ttl': 256.0, 'secret': None}

with open('sample2.json', 'r') as f:
    json.dump(a, f, indent=4) # indent 參數可以不加,加了是為了排版
    
s = json.dumps(a, indent=4) 
print(s)