从CSV文件创建字典?

229

我正在尝试从一个csv文件创建一个字典。 csv文件的第一列包含唯一键,第二列包含值。 csv文件的每一行表示字典中的唯一键值对。 我尝试使用csv.DictReadercsv.DictWriter类,但我只能想出如何为每一行生成一个新字典。 我想要一个字典。 这是我正在尝试使用的代码:

import csv

with open('coors.csv', mode='r') as infile:
    reader = csv.reader(infile)
    with open('coors_new.csv', mode='w') as outfile:
    writer = csv.writer(outfile)
    for rows in reader:
        k = rows[0]
        v = rows[1]
        mydict = {k:v for k, v in rows}
    print(mydict)

当我运行上面的代码时,我收到一个ValueError:too many values to unpack (expected 2)错误。 我该如何从csv文件中创建一个字典?谢谢。


3
你能否举一个输入文件和其对应的数据结构的例子? - robert
1
当您迭代csv.reader时,您会得到单行而不是多行。 因此,有效的形式是mydict = {k:v for k,v in reader} 但是,如果您确定csv文件中只有两列,则mydict = dict(reader)速度更快。 - Alex Laskin
请注意,将字典/键值数据存储在CSV文件中并非没有问题(例如处理混合类型列)。在我看来,JSON格式可以更好地表示这种类型的数据。 - mirekphd
18个回答

229

我相信你要找的语法应该如下:

import csv

with open('coors.csv', mode='r') as infile:
    reader = csv.reader(infile)
    with open('coors_new.csv', mode='w') as outfile:
        writer = csv.writer(outfile)
        mydict = {rows[0]:rows[1] for rows in reader}

另外,对于 Python <= 2.7.1,您需要:

mydict = dict((rows[0],rows[1]) for rows in reader)

2
好的,对于超出预期长度的行进行计数是不错的做法;但是如果一行中有太多的项,他不应该自己引发异常吗?我认为这意味着输入数据存在错误。 - machine yearning
1
然后他至少可以将异常缩小到有问题的输入。 - machine yearning
这个观点有一定的道理,但我坚信异常是用来告诉你程序存在错误的,而不是当世界给你柠檬时使用。这时候你应该打印一个漂亮的错误信息并失败,或者更适合这种情况的是打印一个漂亮的警告信息并成功。 - Nate
抱歉,我查看了操作者的代码,很难确定他是否只想每行显示2个项目。我错了! - machine yearning
3
我在CSV文件中有多行数据,但只生成了一个键值对。 - Abhilash Mishra
我一整晚都在寻找这个。我有一个使用flask/SQLAlchemy的API,我想用一个文本文件来模拟它,并且只需对读取器输出使用jsonify - 这就是魔法代码,谢谢! - Nick.McDermaid

143

通过调用open函数,并使用csv.DictReader打开文件。

input_file = csv.DictReader(open("coors.csv"))
您可以通过迭代input_file来迭代csv文件dict reader对象的行。
for row in input_file:
    print(row)

或者只访问第一行

dictobj = csv.DictReader(open('coors.csv')).next() 

更新 在 Python 3+ 版本中,这段代码会略有不同:

reader = csv.DictReader(open('coors.csv'))
dictobj = next(reader) 

13
这意味着DictReader对象不是一个字典(不是键值对)。 - HN Singh
3
@HN Singh - 是的,我知道 - 我的意图是希望它也能帮助其他人。 - Laxmikant Ratnaparkhi
1
'DictReader' 对象没有 'next' 属性。 - Palak Bansal
3
@Palak - 这是针对Python 2.7的回答,而在Python 3+版本中,请尝试使用next(dictobj)代替dictobj.next() - Laxmikant Ratnaparkhi
1
在Python 3+中,这也可以工作:dictobj = reader.__next__() - Jose R
显示剩余2条评论

76
import csv
reader = csv.reader(open('filename.csv', 'r'))
d = {}
for row in reader:
   k, v = row
   d[k] = v

61
@Alex Laskin: 真的吗?在我看来,这是相当易读的Python代码。你的原则是什么来支持这个说法呢?你基本上只是称他为“便便头”... - machine yearning
38
@machine-yearning,不,我没有说他的代码“糟糕”。 但是,如果可以简单地写成for k, v in reader,为什么要写成for row in reader: k, v = row呢?例如,如果你期望reader是一个产生两个元素项的可迭代对象,那么你可以直接将它传递给dict进行转换。 在大型数据集上,d = dict(reader)更短且速度显著更快。 - Alex Laskin
59
@Alex Laskin:感谢澄清。我个人同意你的看法,但是如果你要称某人的代码为“不符合Python风格”,那么你应该给出相应的理由。我认为,“更短”和“更快”并不一定等同于“更符合Python风格”。可读性和可靠性也是一个巨大的问题。如果我们能够把一些约束条件更容易地融入到上述的for row in reader范式中,那么在长期开发后,它可能会更加实用。我同意你的短期看法,但要注意避免过早优化。 - machine yearning
2
@robert:谢谢老兄!真的帮了我大忙。其他代码太难读了。 - Ash

50

虽然不太优雅,但可以使用Pandas实现一行代码解决。

import pandas as pd
pd.read_csv('coors.csv', header=None, index_col=0, squeeze=True).to_dict()

如果你想为索引指定dtype(如果使用index_col参数在read_csv中无法指定,因为有一个bug):
import pandas as pd
pd.read_csv('coors.csv', header=None, dtype={0: str}).set_index(0).squeeze().to_dict()

6
在我的书中,这是最好的答案。 - boardtc
1
如果有标题呢? - ndtreviv
2
@ndtreviv,您可以使用skiprows来忽略标题。 - mudassirkhan19

22

你只需要将 csv.reader 转换为字典:

~ >> cat > 1.csv
key1, value1
key2, value2
key2, value22
key3, value3

~ >> cat > d.py
import csv
with open('1.csv') as f:
    d = dict(filter(None, csv.reader(f)))

print(d)

~ >> python d.py
{'key3': ' value3', 'key2': ' value22', 'key1': ' value1'}

9
如果他能确信输入数据中的每一行都不会包含三列或更多列,那么这个解决方案是简洁且有效的。但是,如果遇到这种情况,就会引发一个异常,类似于这样:ValueError: dictionary update sequence element #2 has length 3; 2 is required - Nate
@机器,从问题中的错误来看,CSV文件有超过2列。 - John La Rooy
@gnibbler,不是的。问题出在对行进行了双重解包(double unpacking)。首先,他尝试迭代reader,获取rows,实际上是单个row。然后当他尝试迭代该单个行时,获得两个项目,这些项目无法正确地解包。 - Alex Laskin
一个普遍的评论:从可迭代对象中创建保存在内存中的对象可能会导致内存问题。建议检查您的内存空间和可迭代源文件的大小。可迭代对象的一个主要优点(整个重点?)是不将大型内容保存在内存中。 - travelingbones
@Nate: 如果需要的话,可以通过将filter调用包装在 map(operator.itemgetter(slice(2)), ...) 中来解决此问题,这样它只会提取前两个项,使其变为:dict(map(operator.itemgetter(slice(2)), filter(None, csv.reader(f))))。如果是Python 2,请确保使用 from future_builtins import map, filter,这样 dict 就会直接读取生成器,而不会先产生多个不必要的临时列表。 - ShadowRanger
这非常简洁!谢谢@Alex Laskin - amc

17
假设您有这样格式的CSV文件:
"a","b"
1,2
3,4
5,6

并且你希望输出结果为:

[{'a': '1', ' "b"': '2'}, {'a': '3', ' "b"': '4'}, {'a': '5', ' "b"': '6'}]

一个尚未提及的zip函数非常简单且相当有用。

def read_csv(filename):
    with open(filename) as f:
        file_data=csv.reader(f)
        headers=next(file_data)
        return [dict(zip(headers,i)) for i in file_data]

如果你更喜欢pandas,它也可以很好地完成这个任务:

import pandas as pd
def read_csv(filename):
    return pd.read_csv(filename).to_dict('records')

2
它适用于我的使用情况。 - user3928562
好的例子和解决方案。在这种情况下,熊猫解决方案也很容易阅读。 - undefined

14

你也可以使用NumPy来做到这一点。

from numpy import loadtxt
key_value = loadtxt("filename.csv", delimiter=",")
mydict = { k:v for k,v in key_value }

1
请注意,此方法仅适用于数字列。对于非数字列,您将会收到“ValueError: could not convert string to float: 'Name'” 的错误提示。 - mirekphd

11

一行代码解决方案

import pandas as pd

dict = {row[0] : row[1] for _, row in pd.read_csv("file.csv").iterrows()}

注意:这会掩盖内置的 dict 对象(您将无法再使用它 :) - mirekphd

8

对于简单的 csv 文件,例如以下文件

id,col1,col2,col3
row1,r1c1,r1c2,r1c3
row2,r2c1,r2c2,r2c3
row3,r3c1,r3c2,r3c3
row4,r4c1,r4c2,r4c3

你可以仅使用内置函数将其转换为Python字典

with open(csv_file) as f:
    csv_list = [[val.strip() for val in r.split(",")] for r in f.readlines()]

(_, *header), *data = csv_list
csv_dict = {}
for row in data:
    key, *values = row   
    csv_dict[key] = {key: value for key, value in zip(header, values)}

这将产生以下字典。
{'row1': {'col1': 'r1c1', 'col2': 'r1c2', 'col3': 'r1c3'},
 'row2': {'col1': 'r2c1', 'col2': 'r2c2', 'col3': 'r2c3'},
 'row3': {'col1': 'r3c1', 'col2': 'r3c2', 'col3': 'r3c3'},
 'row4': {'col1': 'r4c1', 'col2': 'r4c2', 'col3': 'r4c3'}}

注意:Python字典具有唯一键,因此如果您的csv文件有重复的ids,则应将每行附加到列表中。
for row in data:
    key, *values = row

    if key not in csv_dict:
            csv_dict[key] = []

    csv_dict[key].append({key: value for key, value in zip(header, values)})

注意:这可以缩短为使用 set_default:csv_dict.set_default(key, []).append({key: value for key, value in zip(header, values)})) - mdmjsh
1
你的.append命令中的({key: value})语法非常有用。当我迭代并添加到从CSV文件创建的DictReader对象时,我最终在row.update中使用了相同的语法。 - Shrout1
@mdmjsh 这是什么?还有,set_default 没有这样的命令。 - flywire
那是一个打字错误,它应该是setdefault - 这并不改变上面的正确答案,只是意味着可以排除 'if key not in csv_dict...' 逻辑。在动态构建字典时,我经常使用 setdefault。 - mdmjsh

5
我建议添加 if rows 以防止文件末尾有空行。
import csv
with open('coors.csv', mode='r') as infile:
    reader = csv.reader(infile)
    with open('coors_new.csv', mode='w') as outfile:
        writer = csv.writer(outfile)
        mydict = dict(row[:2] for row in reader if row)

既做得好又考虑周全。但正如我上面所说,他真的应该忽略输入行比他预期的更长这一事实吗?如果他得到了一个超过两个项目的行,我会建议他引发自己的异常(带有自定义消息)。 - machine yearning
或者,如@Nate所述,至少打印一个警告消息。这似乎不是您想忽略的事情。 - machine yearning
你的回答使我陷入沉思 - 在这种情况下,切片和索引之间是否存在效率差异? - Nate
1
@机器,我不知道。也许它是来自数据库的用户表的转储,并且他只是想要一个userid:username的字典之类的东西,这只是一个例子。 - John La Rooy
@Nate,我会预计元组(你的方式)会稍微快一点。 - John La Rooy
1
嘿,大家好,感谢你们的评论。你们的讨论真的帮助了我解决问题。我喜欢关于如果输入超出预期则引发标志的想法。我的数据是数据库转储,我确实有超过两列的数据。 - drbunsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接