原文:https://pythoncaff.com/docs/pymotw/csv-comma-separated-value-files/125
csv
模塊主要用於處理從電子數據表格或數據庫中導入到文本文件的數據,通常簡稱為comma-separated value (CSV)格式因為逗號用於分離每條記錄的各個字段。
讀取##
從 CSV 文件中讀取數據, 可以使用 reader()
函數來創建一個讀取對象。 這個讀取對象順序處理文件的每一行,可以把它當成迭代器使用, 例如:
csv_reader.py
import csv
import sys
with open(sys.argv[1], 'rt') as f: reader = csv.reader(f) for row in reader: print(row)
reader()
的第一個參數指源文本,在這個例子中,是一個文件,但它可以是任何可迭代對象( StringIO
實例,list
等)。第二個參數是可選的,可用於控制輸入的數據如何被解析。
"Title 1","Title 2","Title 3","Title 4" 1,"a",08/18/07,"å" 2,"b",08/19/07,"∫" 3,"c",08/20/07,"ç"
它被讀取時,輸入數據的每一行被轉換為一個字符串列表。
$ python3 csv_reader.py testdata.csv ['Title 1', 'Title 2', 'Title 3', 'Title 4'] ['1', 'a', '08/18/07', 'å'] ['2', 'b', '08/19/07', '∫'] ['3', 'c', '08/20/07', 'ç']
解析器會自動處理嵌入在一行字符串中的換行符,這也是輸出中的一行數據可能和輸入的一行不同的原因。
"Title 1","Title 2","Title 3" 1,"first line second line",08/18/07
輸入中帶有換行符的字段在解析器返回時保留內部換行符。
$ python3 csv_reader.py testlinebreak.csv ['Title 1', 'Title 2', 'Title 3'] ['1', 'first line\nsecond line', '08/18/07']
寫入##
寫入 CSV 文件和讀取它們一樣簡單。使用 writer()
方法創建一個寫入對象,然后使用 writerow()
去輸出每一行。
csv_writer.py
import csv
import sys
unicode_chars = 'å∫ç' with open(sys.argv[1], 'wt') as f: writer = csv.writer(f) writer.writerow(('Title 1', 'Title 2', 'Title 3', 'Title 4')) for i in range(3): row = ( i + 1, chr(ord('a') + i), '08/{:02d}/07'.format(i + 1), unicode_chars[i], ) writer.writerow(row) print(open(sys.argv[1], 'rt').read())
這個例子的輸出和上面讀取的例子看起來有些不同,是因為這里有的值沒有加引號。
$ python3 csv_writer.py testout.csv Title 1,Title 2,Title 3,Title 4 1,a,08/01/07,å 2,b,08/02/07,∫ 3,c,08/03/07,ç
引用##
寫入時,默認的引用行為不同,所以之前示例中的第二和第三個字段未被引用。 要添加引號,請將 quoting
參數設置為其他引用模式。
writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)
在這個例子中, QUOTE_NONNUMERIC
會給所有字段值不是數字的值添加引號。
$ python3 csv_writer_quoted.py testout_quoted.csv "Title 1","Title 2","Title 3","Title 4" 1,"a","08/01/07","å" 2,"b","08/02/07","∫" 3,"c","08/03/07","ç"
有四種不同的引用選項,在 csv 模塊中被定義為常量。
QUOTE_ALL
無論什么類型的字段都會被引用。
QUOTE_MINIMAL
這是默認的選項, 使用指定的字符引用各字段(如果解析器被配置為相同的dialect和選項時,可能會讓解析器在解析時產生混淆)。
QUOTE_NONNUMERIC
引用那些不是整數或浮點數的字段。當使用讀取對象時, 如果輸入的字段是沒有引號的, 那么它們會被轉換成浮點數。
QUOTE_NONE
對所有的輸出內容都不加引用,當使用讀取對象時,引用字符看作是包含在每個字段的值里(但在正常情況下,它們被當成定界符而被去掉)。
編碼風格##
其實沒有一個標准定義這類逗號分隔值的文件,所以解析器需要很靈活,通過很多參數去控制如何解析 csv
或給其寫入數據。但這並不是每個參數在寫入或讀取 csv 時分別傳入,而是統一分組為一個 編碼風格 對象。
Dialect 類可以通過名字注冊,因此 csv
模塊調用它時不必預先知道相關的參數設置。所有注冊過的編碼風格列表可以通過 list_dialects()
方法查看。
csv_list_dialects.py
import csv print(csv.list_dialects())
標准庫提供了三種編碼風格,分別為: excel
, excel-tabs
和 unix
。 excel
編碼風格用來處理默認來自 Microsoft Excel 格式的數據的,同樣可用於處理來自 LibreOffice 格式的。 unix
編碼風格將所有字段通過雙引號引用,並用 \n
做為每條記錄的分隔符。
$ python3 csv_list_dialects.py ['excel', 'excel-tab', 'unix']
創建一個編碼風格##
如果不使用逗號分隔字段,輸入文件使用豎杠( |
),就像這樣
"Title 1"|"Title 2"|"Title 3" 1|"first line second line"|08/18/07
一個新的編碼風格可以使用不同的分隔符進行注冊。
csv_dialect.py
import csv csv.register_dialect('pipes', delimiter='|') with open('testdata.pipes', 'r') as f: reader = csv.reader(f, dialect='pipes') for row in reader: print(row)
使用「豎杠」的編碼風格,可以像使用逗號一樣讀取文件。
$ python3 csv_dialect.py ['Title 1', 'Title 2', 'Title 3'] ['1', 'first line\nsecond line', '08/18/07']
編碼風格參數##
編碼風格指定解析或寫入數據文件時使用的所有標記。下表列出了可以設定的屬性,從字段的分隔方式到用於轉義標記的字符。
CSV 編碼風格參數
屬性 | 默認 | 含義 |
---|---|---|
delimiter | , |
字段分隔符(單字符) |
doublequote | True | 控制 quotechar 實例是否翻倍 |
escapechar | None | 用於表示轉義序列的字符 |
lineterminator | \r\n |
寫入時用來換行的字符 |
quotechar | " |
引用含特殊值字段的字符(一個字符) |
quoting | QUOTE_MINIMAL |
控制前面表述的引用行為 |
skipinitialspace | False | 是否在字段分隔符后忽略空格 |
csv_dialect_variations.py
import csv import sys csv.register_dialect('escaped', escapechar='\\', doublequote=False, quoting=csv.QUOTE_NONE, ) csv.register_dialect('singlequote', quotechar="'", quoting=csv.QUOTE_ALL, ) quoting_modes = { getattr(csv, n): n for n in dir(csv) if n.startswith('QUOTE_') } TEMPLATE = '''\ Dialect: "{name}" delimiter = {dl!r:<6} skipinitialspace = {si!r} doublequote = {dq!r:<6} quoting = {qu} quotechar = {qc!r:<6} lineterminator = {lt!r} escapechar = {ec!r:<6} ''' for name in sorted(csv.list_dialects()): dialect = csv.get_dialect(name) print(TEMPLATE.format( name=name, dl=dialect.delimiter, si=dialect.skipinitialspace, dq=dialect.doublequote, qu=quoting_modes[dialect.quoting], qc=dialect.quotechar, lt=dialect.lineterminator, ec=dialect.escapechar, )) writer = csv.writer(sys.stdout, dialect=dialect) writer.writerow( ('col1', 1, '10/01/2010', 'Special chars: " \' {} to parse'.format( dialect.delimiter)) ) print()
這段程序演示了當使用幾種不同的編碼風格格式化時,相同的數據如何展示。
$ python3 csv_dialect_variations.py Dialect: "escaped" delimiter = ',' skipinitialspace = 0 doublequote = 0 quoting = QUOTE_NONE quotechar = '"' lineterminator = '\r\n' escapechar = '\\' col1,1,10/01/2010,Special chars: \" ' \, to parse Dialect: "excel" delimiter = ',' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_MINIMAL quotechar = '"' lineterminator = '\r\n' escapechar = None col1,1,10/01/2010,"Special chars: "" ' , to parse" Dialect: "excel-tab" delimiter = '\t' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_MINIMAL quotechar = '"' lineterminator = '\r\n' escapechar = None col1 1 10/01/2010 "Special chars: "" ' to parse" Dialect: "singlequote" delimiter = ',' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_ALL quotechar = "'" lineterminator = '\r\n' escapechar = None 'col1','1','10/01/2010','Special chars: " '' , to parse' Dialect: "unix" delimiter = ',' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_ALL quotechar = '"' lineterminator = '\n' escapechar = None "col1","1","10/01/2010","Special chars: "" ' , to parse"
自動檢測編碼風格##
配置一個輸入文件的編碼風格的最好的辦法是提前知道哪種編碼風格是正確的。對於那些編碼風格未知的參數, Sniffer
類可用於做有效的猜測。 sniff()
方法會獲取輸入數據的一個樣本和一個可選參數,給出可能的分隔符。
csv_dialect_sniffer.py
import csv from io import StringIO import textwrap csv.register_dialect('escaped', escapechar='\\', doublequote=False, quoting=csv.QUOTE_NONE) csv.register_dialect('singlequote', quotechar="'", quoting=csv.QUOTE_ALL) # 為所有已知的編碼風格生成樣本數據 samples = [] for name in sorted(csv.list_dialects()): buffer = StringIO() dialect = csv.get_dialect(name) writer = csv.writer(buffer, dialect=dialect) writer.writerow( ('col1', 1, '10/01/2010', 'Special chars " \' {} to parse'.format( dialect.delimiter)) ) samples.append((name, dialect, buffer.getvalue())) # 猜測樣本的編碼風格,然后用猜測結果來解析數據。 sniffer = csv.Sniffer() for name, expected, sample in samples: print('Dialect: "{}"'.format(name)) print('In: {}'.format(sample.rstrip())) dialect = sniffer.sniff(sample, delimiters=',\t') reader = csv.reader(StringIO(sample), dialect=dialect) print('Parsed:\n {}\n'.format( '\n '.join(repr(r) for r in next(reader))))
sniff()
方法返回一個包含了解析數據的參數的 Dialect
實例。結果並不一定是正確的,例如這個例子中的「escaped」。
$ python3 csv_dialect_sniffer.py Dialect: "escaped" In: col1,1,10/01/2010,Special chars \" ' \, to parse Parsed: 'col1' '1' '10/01/2010' 'Special chars \\" \' \\' ' to parse' Dialect: "excel" In: col1,1,10/01/2010,"Special chars "" ' , to parse" Parsed: 'col1' '1' '10/01/2010' 'Special chars " \' , to parse' Dialect: "excel-tab" In: col1 1 10/01/2010 "Special chars "" ' to parse" Parsed: 'col1' '1' '10/01/2010' 'Special chars " \' \t to parse' Dialect: "singlequote" In: 'col1','1','10/01/2010','Special chars " '' , to parse' Parsed: 'col1' '1' '10/01/2010' 'Special chars " \' , to parse' Dialect: "unix" In: "col1","1","10/01/2010","Special chars "" ' , to parse" Parsed: 'col1' '1' '10/01/2010' 'Special chars " \' , to parse'
使用字段名稱##
除了處理數據序列之外,csv
模塊還提供了用於處理將字典作為行的類,以便可以對字段進行命名。 DictReader
和 DictWriter
將行翻譯成字典而不是序列。字典的鍵可以被傳入或者從輸入的第一行(當該行包含標題時)推斷出來。
csv_dictreader.py
import csv import sys with open(sys.argv[1], 'rt') as f: reader = csv.DictReader(f) for row in reader: print(row)
基於字典的讀取器和寫入器被實現為基於序列的類的包裝器,並使用相同的方法和參數。讀取器唯一的區別是行作為 OrderedDict
實例返回而不是列表或者元組(早期的 Python 版本中,行作為常規 dict
實例返回)。
$ python3 csv_dictreader.py testdata.csv OrderedDict([('Title 1', '1'), ('Title 2', 'a'), ('Title 3', '08/18/07'), ('Title 4', 'å')]) OrderedDict([('Title 1', '2'), ('Title 2', 'b'), ('Title 3', '08/19/07'), ('Title 4', '∫')]) OrderedDict([('Title 1', '3'), ('Title 2', 'c'), ('Title 3', '08/20/07'), ('Title 4', 'ç')])
必須要給 DictWriter
一個字段名稱列表,以至於讓它在輸出的時候如何排序。
csv_dictwriter.py
import csv import sys fieldnames = ('Title 1', 'Title 2', 'Title 3', 'Title 4') headers = { n: n for n in fieldnames } unicode_chars = 'å∫ç' with open(sys.argv[1], 'wt') as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() for i in range(3): writer.writerow({ 'Title 1': i + 1, 'Title 2': chr(ord('a') + i), 'Title 3': '08/{:02d}/07'.format(i + 1), 'Title 4': unicode_chars[i], }) print(open(sys.argv[1], 'rt').read())
字段名稱不會被自動寫入到文件,但是他們可以顯示地使用 writeheader()
方法寫入。
$ python3 csv_dictwriter.py testout.csv Title 1,Title 2,Title 3,Title 4 1,a,08/01/07,å 2,b,08/02/07,∫ 3,c,08/03/07,ç