【问题标题】:python: list element in CSV filepython:CSV文件中的列表元素
【发布时间】:2020-05-15 10:48:33
【问题描述】:

我有一个这种结构的csv 文件:

Id,Country,Cities
1,Canada,"['Toronto','Ottawa','Montreal']"
2,Italy,"['Rome','Milan','Naples', 'Palermo']"
3,France,"['Paris','Cannes','Lyon']"
4,Spain,"['Seville','Alicante','Barcelona']"

最后一列包含一个列表,但它被表示为一个字符串,因此它被视为单个元素。解析文件时,我需要将此元素作为list,而不是字符串。到目前为止,我已经找到了转换它的方法:

L = "['Toronto','Ottawa','Montreal']"
seq = ast.literal_eval(L)

由于我是python 的新手,我的问题是——这是正常的执行方式,还是有一种正确 的方式来表示 CSV 中的列表,这样我就不会必须做转换,还是有更简单的转换方法?

谢谢!

【问题讨论】:

标签: python python-3.x list csv


【解决方案1】:

使用ast.literal_eval(...) 会起作用,但它需要其他CSV 读取软件无法识别的特殊语法,并且使用eval 语句,这是一个危险信号。

使用 eval 可能很危险,即使在这种情况下您使用的是更安全的 literal_eval 选项,它比原始的 eval 函数更受限制。

通常您会在单列中有多个值的 CSV 文件中看到它们会使用简单的分隔符并引用字段。

例如:

ID,Country,Cities
1,Canada,"Toronto;Ottawa;Montreal"

然后在 python 或任何其他语言中,无需求助于eval 就可以轻松阅读:

import csv

with open("data.csv") as fobj:
    reader = csv.reader(fobj)
    field_names = next(reader)

    rows = []
    for row in reader:
        row[-1] = row[-1].split(";")
        rows.append(row)

ast.literal_eval 的问题

尽管ast.literal_eval 函数比在用户输入上使用常规eval 更安全,但它仍然可能被利用。 documentation for literal_eval 有这个警告:

警告:由于 Python 的 AST 编译器中的堆栈深度限制,使用足够大/复杂的字符串可能会使 Python 解释器崩溃。

这个can be found here的演示:

>>> import ast
>>> ast.literal_eval("()" * 10 ** 6)
[1]    48513 segmentation fault  python

我绝对不是专家,但让用户能够使程序崩溃并可能利用一些模糊的内存漏洞是不好的,在这种用例中可以避免。

如果您想使用literal_eval 的原因是为了获得正确的输入,并且您确信输入数据是 100% 可信的,那么我想它可以使用。但是,您始终可以包装该函数以执行一些健全性检查:

def sanely_eval(value: str, max_size: int = 100_000) -> object:
    if len(value) > max_size:
        raise ValueError(f"len(value) is greater than the max_size={max_size!r}")
    return ast.literal_eval(value)

但是,根据您创建和使用 CSV 文件的方式,这可能会降低数据的可移植性,因为它是一种特定于 python 的格式。

【讨论】:

  • 感谢您的反馈。您可能是说rows.append(row[-1].split(";")),否则它会抱怨AttributeError: 'list' object has no attribute 'split'
  • @Mark 感谢您发现这一点!我已经更新了我的示例。该代码与您的代码略有不同,因此它保留了整行,而不仅仅是 cities 列。
  • 你能解释一下为什么eval 声明是一个很大的危险信号吗?它被认为是危险的吗?
  • @Mark 我已经在我的答案中添加了细节。我假设您的意思是literal_eval,而不是eval。如果您想了解为什么打电话给eval("user input...") 是危险的,请看这里:nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
【解决方案2】:

如果您可以控制 CSV,则可以将项目与某个不会出现在城市中且不是逗号的其他已知字符分开。说冒号 (:)。

例如,第一行将如下所示:

1,Canada,Toronto:Ottawa:Montreal

在处理数据时,您将拥有整个元素,并且您可以这样做

cities.split(':')

如果你想另辟蹊径(你有一个 Python 列表中的城市,并且你想创建这个字符串)你可以使用join()

':'.join(['Toronto', 'Ottawa', 'Montreal'])

【讨论】:

  • 用特殊字符分隔是在发明轮子。 CSV 标准具有处理内部分隔符的双引号的概念。对于 TS,如果是 .split 或反序列化 json 没有区别
【解决方案3】:

对于 csv 的具体结构,您可以像这样将城市转换为列表:

cities = '''"['Rome','Milan','Naples', 'Palermo']"'''

cities = cities[2:-2]  # remove "[ and ]"

print(cities)  # 'Rome','Milan','Naples', 'Palermo'

cities = cities.split(',')  # convert to list

print(cities)  # ["'Rome'", "'Milan'", "'Naples'", " 'Palermo'"]

cities = [x.strip() for x in cities]  # remove leading or following spaces (if exists)

print(cities)  # ["'Rome'", "'Milan'", "'Naples'", "'Palermo'"]

cities = [x[1:-1] for x in cities]  # remove quotes '' from each city

print(cities)  # ['Rome', 'Milan', 'Naples', 'Palermo']

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-05
    • 1970-01-01
    • 2011-11-09
    相关资源
    最近更新 更多