如何用Pythonic的方式将CSV文件数据读取为命名元组的行?

38

如何最佳地读取包含标题行的数据文件并将其读入命名元组,以便可以通过标题名称访问数据行?

我尝试了类似于这样的代码:

import csv
from collections import namedtuple

with open('data_file.txt', mode="r") as infile:
    reader = csv.reader(infile)
    Data = namedtuple("Data", ", ".join(i for i in reader[0]))
    next(reader)
    for row in reader:
        data = Data(*row)

reader对象不可下标索引,所以上述代码会引发TypeError。如何以Pythonic方式将文件头读入namedtuple中?

3个回答

49

用途:

Data = namedtuple("Data", next(reader))

并且省略这一行:

next(reader)

将这个与基于Martineau下方评论的迭代版本结合起来,示例变为Python 2的版本

import csv
from collections import namedtuple
from itertools import imap

with open("data_file.txt", mode="rb") as infile:
    reader = csv.reader(infile)
    Data = namedtuple("Data", next(reader))  # get names from column headers
    for data in imap(Data._make, reader):
        print data.foo
        # ...further processing of a line...

还有适用于Python 3的版本

import csv
from collections import namedtuple

with open("data_file.txt", newline="") as infile:
    reader = csv.reader(infile)
    Data = namedtuple("Data", next(reader))  # get names from column headers
    for data in map(Data._make, reader):
        print(data.foo)
        # ...further processing of a line...

2
drbunsen: 在进行了这个操作之后,你可以把处理循环改为:for data in map(Data._make, reader): - martineau
@Jean-FrançoisFabre,我撤销了您的更改,因为生成的代码对Python 2和Python 3都是错误的。在Python 2中,需要mode="rb",而在Python 3中需要newline="" - Sven Marnach
@SvenMarnach 你在写方面是对的,但在读方面不是。做个测试,你就会发现。newline="" 只对一些旧版本的 Python 3 有用,这些版本在每行后插入一个空白行(对于最新的 2.7 版本也是如此,其中 "rb" 不是必需的。请查看我的问答:https://dev59.com/ZlkT5IYBdhLWcg3wStof 并自行测试。open("data_file.txt")(读取时)适用于任何版本的 Python。写入则是另一回事,但在较新的 2 或 3 分支中似乎没有 newline 或 wb 也可以正常工作。 - Jean-François Fabre
1
@Jean-FrançoisFabre 我无法尝试它,因为我没有访问实际上b有所不同的平台,并且我认为这是不必要的。最新的Python 2和Python 3文档中都说明了csv模块的这些要求,因此即使您发现它在某些平台上对某些输入有效,您仍然以未记录的方式使用API,这可能会随时中断。 - Sven Marnach
你关于文档的说法是正确的。有时我可能会问一些问题。Raymond Hettinger正在SO上潜水,他可能会有所发言。 - Jean-François Fabre
显示剩余6条评论

30
请参考csv.DictReader。它提供了从第一行获取列名的功能,正如您所需要的,然后使用字典按名称访问每行中的每个列。
如果出于某种原因仍需要将行作为collections.namedtuple访问,则可以轻松地将字典转换为命名元组,如下所示:
with open('data_file.txt') as infile:
    reader = csv.DictReader(infile)
    Data = collections.namedtuple('Data', reader.fieldnames)
    tuples = [Data(**row) for row in reader]

6
这个解决方案的问题在于每一行都被转换为字典,然后再转换为命名元组。如果不需要中间步骤的字典,则效率低下。 - Chris Cogdon
1
这种方法无法保持顺序,因此你的 CSV 文件中的第一列将成为命名元组中的随机列。到那时,最好使用字典。 - hraban
在我的情况下,列的顺序并不重要,但是命名元组比字典更好,因为它使代码更易读,并且在访问列名时会检查拼写。 - undefined
@JohnHenckel 好久不见,但我想我和Chris在这里的评论背后的想法是,如果你要使用namedtuple,就直接使用Sven提供的被接受的答案。如果你要使用字典,就坚持使用字典。不要从csv转换为字典再转换为namedtuple。我想这就是我们的观点。 - undefined

0
我建议采用以下方法:
import csv
from collections import namedtuple

with open("data.csv", 'r') as f:
        reader = csv.reader(f, delimiter=',')
        Row = namedtuple('Row', next(reader))
        rows = [Row(*line) for line in reader]

如果您使用Pandas,解决方案会变得更加优雅:
import pandas as pd
from collections import namedtuple

data = pd.read_csv("data.csv")
Row = namedtuple('Row', data.columns)
rows = [Row(*row) for index, row in data.iterrows()]

在这两种情况下,您都可以通过字段名称与记录进行交互:
for row in rows:
    print(row.foo)

1
我认为 Row = namedtuple('Row', next(reader)) 不会按照你的方式工作,因为 namedtuple 的第二个参数应该是元组子类的字段名,这些字段名“是一个字符串序列,例如 ['x','y']”,根据文档。你还在循环中重复创建 reader - martineau

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