Python csv.md

时间:2022-04-07 07:36:10

csv

csv模块可以用于处理从电子表格和数据库导出的数据到带有字段和记录格式的文本文件,通常称为逗号分隔值(csv)格式,因为逗号通常用于分隔记录中的字段。

Reading

csv.reader(csvfile, dialect='excel', **fmtparams):返回一个读取器对象,它将在给定的csvfile中迭代。csvfile可以是任何支持iterator协议的对象,并且每次调用__next__()方法时返回一个字符串 - file objects和列表对象都是合适的。如果csvfile是文件对象,则应使用newline=''打开它。可以给出一个可选的方言参数,用于定义特定于某个CSV方言的一组参数。它可以是Dialect类的子类的实例或list_dialects()函数返回的字符串之一。可以给出其他可选的fmtparams关键字参数以覆盖当前方言中的各个格式化参数。从csv文件读取的每一行作为字符串列表返回。除非指定了QUOTE_NONNUMERIC格式选项(在这种情况下未引用的字段转换为浮点型),否则不会执行自动数据类型转换。
举例
从csv文件中读取数据, 可以使用reader()函数来创建一个读取对象. 这个读取对象顺序处理文件的每一行, 可以把它当成迭代器使用, 例如:

import csv
import sys with open(sys.argv[1], 'rt') as f:
reader = csv.reader(f)
for row in reader:
print(row)

reader()的第一个参数指示源文本行, 在这个例子中, 是一个文件, 但它可以是任何可转换的对象(StringIO对象, lists等). 指定其他可选的参数可用于控制输入的数据如何被解析.
下面是测试的csv数据:

"Title 1","Title 2","Title 3","Title 4"
1,"a",08/18/07,"å"
2,"b",08/19/07,"∫"
3,"c",08/20/07,"ç"

它被读取时, 输入数据的每一行被转换为一个字符串列表.Linux输出:

# python csv_study.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', 'ç']

如果你知道特定的列具有特定的类型, 你就可以自行转换, 但csv不会自动转换. 它会自动处理嵌入在一行字符串中(这个行和输入源文件的”行”意思是不同的)的换行符.

# cat testdata_1.csv
"Title 1","Title 2","Title 3","Title 4"
1,"first line
second line",08/18/07

Linux输出:

# python csv_study.py testdata_1.csv
['Title 1', 'Title 2', 'Title 3', 'Title 4']
['1', 'first line\nsecond line', '08/18/07']

Writing

csv.writer(csvfile, dialect='excel', **fmtparams):返回一个writer对象,负责将用户的数据转换为给定类文件对象上的分隔字符串。csvfile可以是具有write()方法的任何对象。如果csvfile是文件对象,则应使用newline=' ' 打开。可以给出可选的方言参数,用于定义特定于特定CSV方言的一组参数。它可以是Dialect类的子类的实例或list_dialects()函数返回的字符串之一。可以给出其他可选的fmtparams关键字参数以覆盖当前方言中的各个格式化参数。为了尽可能容易地与实现DB API的模块接口,值None被写为空字符串。虽然这不是可逆转换,但它可以更轻松地将SQL NULL数据值转储到CSV文件,而无需预处理从cursor.fetch*调用返回的数据。所有其他非字符串数据在写入之前用str()进行字符串化。
csvwriter.writerow(row):将行参数写入写入程序的文件对象,根据当前方言格式化。
csvwriter.writerows(rows):将所有行参数(如上所述的行对象的列表)写入作者的文件对象,根据当前方言格式化。
csvwriter.dialect:作者使用的方言的只读描述。
DictWriter.writeheader():用字段名写入一行(在构造函数中指定)。

举例
当你想把数据导入到其他应用程序中, 对CSV文件的写入也是非常方便的. 使用writer()函数来创建一个写入对象, 对于每一行, 使用writerow()来输出一行.

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())

Linux 输出:

# python csv_study.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

还有4种不同的引用选项, 它们作为常量定义在csv模块中.
QUOTE_ALL:不管是什么类型, 任何内容都加上引号
QUOTE_MINIMAL:这是默认的, 使用指定的字符引用各个域(如果解析器被配置为相同的dialect和选项时, 可能会让解析器在解析时产生混淆)
QUOTE_NONNUMERIC:引用那些不是整数或浮点数的域. 当使用读取对象时, 如果输入的域是没有引号, 那么它们会被转换成浮点数.
QUOTE_NONE:对所有的输出内容都不加引用, 当使用读取对象时, 引用字符看作是包含在每个域的值里(但在正常情况下, 他们被当成定界符而被去掉)

举例
The default quoting behavior is different for the writer, so the second and third columns in the previous example are not quoted. To add quoting, set the quoting argument to one of the other quoting modes.

import sys
import csv
unicode_chars = 'å∫ç'
with open(sys.argv[1], 'wt') as f:
writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)
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())

In this case, QUOTE_NONNUMERIC adds quotes around all columns that contain values that are not numbers.
输出:

"Title 1","Title 2","Title 3","Title 4"
1,"a","08/01/07","å"
2,"b","08/02/07","∫"
3,"c","08/03/07","ç"

Dialects

对于逗号分隔值文件,没有定义良好的标准,因此解析器需要灵活。这种灵活性意味着有许多参数可以控制csv如何解析或写入数据。它们不是将这些参数分别传递给阅读器和写入器,而是将它们组合成一个 dialect object.
Dialect classes可以通过名称进行注册,因此csv模块的调用者不需要预先知道参数设置。可以用list_dialects()检索完整的注册方言列表。

import csv
print(csv.list_dialects())

输出:

['unix', 'excel', 'excel-tab']

标准库包括三种方言:excel、excel标签和unix。excel dialect用于处理Microsoft excel的默认导出格式的数据,还可以与LibreOffice一起工作。unix dialect使用双引号引用所有字段,并使用\n作为记录分隔符。

Creating a Dialect

举例
如果不使用逗号来限制字段,则输入文件使用管道(|),如下所使用

"Title 1"|"Title 2"|"Title 3"
1|"first line
second line"|08/18/07

可以使用适当的分隔符来注册一种新的dialect。

csv.register_dialect('pipes', delimiter='|')

with open('testdata.pipes', 'r') as f:
reader = csv.reader(f, dialect='pipes')
for row in reader:
print(row)

输出:

['Title 1', 'Title 2', 'Title 3']
['1', 'first line\nsecond line', '08/18/07']

Dialect Parameters

一种dialect 指定解析或写入数据文件时使用的所有标记。下面的表列出了可以指定的文件格式的各个方面,从分隔列的方式到用于转义令牌的字符。

Attribute Default Meaning
delimiter , 用于分隔字段的单字符字符串。它默认为','。
doublequote True 控制在字段中出现的quotechar实例本身应如何引用。当True时,字符加倍。当False时,escapechar用作quotechar的前缀。默认为True。
escapechar None 写入器将分隔符(如果引用)转义的一个字符串设置为QUOTE_NONE和quotechar如果doublequote是False。读取时,escapechar会删除以下字符中的任何特殊含义。它默认为None,它禁用转义。
lineterminator \r\n 用于终止由writer生成的行的字符串。它默认为'\r\n'。
quotechar " 用于引用包含特殊字符(例如分隔符或quotechar)或包含换行字符的字段的单字符字符串。它默认为'"'。
quoting QUOTE_MINIMAL 控制何时报价应由作者生成并由读者识别。它可以接受任何QUOTE_*常量(参见Module Contents一节),默认为QUOTE_MINIMAL。
skipinitialspace False 当True时,紧跟分隔符后的空格将被忽略。默认值为False。

举例

import sys
import csv 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(
('coll', 1, '10/01/2010',
'Special chars: " \' {} to parse'.format(dialect.delimiter))
)
print()

这个程序显示了在使用不同的方言进行格式化时相同的数据是如何出现的。Linux输出:

Dialect: "escaped"

    delimiter   = ','        skipinitialspace  = 0
doublequote = 0 quoting = QUOTE_NONE
quotechar = '"' lineterminator = '\r\n'
escapechar = '\\' coll,1,10/01/2010,Special chars: \" ' \, to parse Dialect: "excel" delimiter = ',' skipinitialspace = 0
doublequote = 1 quoting = QUOTE_MINIMAL
quotechar = '"' lineterminator = '\r\n'
escapechar = None coll,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 coll 1 10/01/2010 "Special chars: "" ' to parse" Dialect: "singlequote" delimiter = ',' skipinitialspace = 0
doublequote = 1 quoting = QUOTE_ALL
quotechar = "'" lineterminator = '\r\n'
escapechar = None 'coll','1','10/01/2010','Special chars: " '' , to parse' Dialect: "unix" delimiter = ',' skipinitialspace = 0
doublequote = 1 quoting = QUOTE_ALL
quotechar = '"' lineterminator = '\n'
escapechar = None "coll","1","10/01/2010","Special chars: "" ' , to parse"

Automatically Detecting Dialects

csv.register_dialect(name[, dialect[, **fmtparams]]):将方言与名称相关联。名称必须是字符串。方言可以通过传递Dialect的子类或fmtparams关键字参数或两者来指定,其中关键字参数覆盖方言的参数。
csv.unregister_dialect(name):从方言注册表中删除与名称关联的方言。如果名称不是注册的方言名称,则会引发Error。
csv.get_dialect(name):返回与名称关联的方言。如果名称不是注册的方言名称,则会引发Error。此函数返回不可变的Dialect。
csv.list_dialects():返回所有注册方言的名称。
csv.field_size_limit([new_limit]):返回解析器允许的当前最大字段大小。如果给出new_limit,则这将成为新限制。

配置一个用于解析输入文件的方言的最佳方法是预先知道正确的设置。对于方言参数未知的数据,Sniffer 类可以用来做一个有根据的猜测。sniff()方法接受输入数据的一个示例,以及一个可选参数,提供可能的分隔符字符。
举例

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(
('coll', 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”的方言所演示的那样。Linux输出:

Dialect: "escaped"
In: coll,1,10/01/2010,Special chars: \" ' \, to parse
Parsed:
'coll'
'1'
'10/01/2010'
'Special chars: \\" \' \\'
' to parse' Dialect: "excel"
In: coll,1,10/01/2010,"Special chars: "" ' , to parse"
Parsed:
'coll'
'1'
'10/01/2010'
'Special chars: " \' , to parse' Dialect: "excel-tab"
In: coll 1 10/01/2010 "Special chars: "" ' to parse"
Parsed:
'coll'
'1'
'10/01/2010'
'Special chars: " \' \t to parse' Dialect: "singlequote"
In: 'coll','1','10/01/2010','Special chars: " '' , to parse'
Parsed:
'coll'
'1'
'10/01/2010'
'Special chars: " \' , to parse' Dialect: "unix"
In: "coll","1","10/01/2010","Special chars: "" ' , to parse"
Parsed:
'coll'
'1'
'10/01/2010'
'Special chars: " \' , to parse'

Using Field Names

class csv.DictReader(csvfile, fieldnames=None, restkey=None, restval=None, dialect='excel', *args, **kwds):创建一个对象,其操作类似于普通读取器,但将读取的信息映射到一个dict中,其中的键由可选的fieldnames参数给出。fieldnames参数是一个sequence,其元素按顺序与输入数据的字段相关联。这些元素成为结果字典的键。如果省略fieldnames参数,则csvfile的第一行中的值将用作字段名称。如果读取的行具有比字段名序列更多的字段,则剩余数据将作为键值为restkey的序列添加。如果读取的行具有比字段名序列少的字段,则剩余的键使用可选的restval参数的值。任何其他可选或关键字参数都传递给底层的reader实例。
class csv.DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', dialect='excel', *args, **kwds)
创建一个操作类似于常规writer的对象,但将字典映射到输出行。fieldnames参数是一个sequence,用于标识传递给writerow()方法的字典中的值被写入csvfile。如果字典在fieldnames中缺少键,则可选的restval参数指定要写入的值。如果传递给writerow()方法的字典包含fieldnames中未找到的键,则可选的extrasaction参数指示要执行的操作。如果设置为'raise',则会引发ValueError。如果设置为'ignore',则会忽略字典中的额外值。任何其他可选或关键字参数都传递给底层的writer实例。请注意,与DictReader类不同,DictWriter的fieldnames参数不是可选的。由于Python的dict对象没有排序,因此没有足够的信息来推断将该行写入到csvfile的顺序。

举例
另外, 在处理数据序列时, csv模块包含了一些将行作为字典进行处理的类. 类DictReader和类DictWriter将每一行转成字典对象, 可以传递字典键值, 或者从输入文件的第一行中推断出键值.

import sys
import csv with open(sys.argv[1], 'rt') as f:
reader = csv.DictReader(f)
for row in reader:
print(row)

基于字典的读取和写入对象可以当作是基于序列对象的进一步实现, 它们使用相同的参数和API. 唯一的差别就是前者把每一行当成是字典而不是列表或元组.Linux输出:

# python csv_study.py testdata.csv
{'Title 2': 'first line\nsecond line', 'Title 1': '1', 'Title 3': '08/18/07', 'Title 4': None}
{'Title 2': 'a', 'Title 1': '1', 'Title 3': '08/18/07', 'Title 4': 'å'}
{'Title 2': 'b', 'Title 1': '2', 'Title 3': '08/19/07', 'Title 4': '∫'}
{'Title 2': 'c', 'Title 1': '3', 'Title 3': '08/20/07', 'Title 4': 'ç'}

DictWriter必须指定一个域名字的列表, 因为这样它才在输出时知道每个列的顺序.

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())

输出:

Title 1,Title 2,Title 3,Title 4
1,a,08/01/07,å
2,b,08/02/07,∫
3,c,08/03/07,ç