如何将列表的字符串表示转换为列表

883

我在思考如何将以下字符串表示的列表转换为list的最简单方法:

x = '[ "A","B","C" , " D"]'

即使在用户在逗号之间放置空格,并且在引号内部也放置空格的情况下,我也需要处理并将其转换为:
x = ["A", "B", "C", "D"] 

我知道可以使用strip()split()去除空格并检查非字母字符。但是代码变得非常混乱。是否有我不知道的快速函数?

21个回答

1186
>>> import ast
>>> x = '[ "A","B","C" , " D"]'
>>> x = ast.literal_eval(x)
>>> x
['A', 'B', 'C', ' D']
>>> x = [n.strip() for n in x]
>>> x
['A', 'B', 'C', 'D']

ast.literal_eval:

评估表达式节点或仅包含Python字面值或容器显示的字符串。提供的字符串或节点只能由以下Python字面结构组成:字符串、字节、数字、元组、列表、字典、集合、布尔值、NoneEllipsis
这可用于评估包含Python值的字符串,而无需自行解析这些值。它不能评估任意复杂的表达式,例如涉及运算符或索引的表达式。

12
根据以下评论,这种做法很危险,因为它会直接运行在字符串中的Python代码。因此,如果有人在其中放置了删除所有内容的调用,则它将毫不犹豫地执行。 - Paul Kenjora
28
你想到了eval,而不是ast.literal_eval - user2357112
38
ast.literal_evaleval更安全,但实际上并不是完全安全的。如最近版本的文档中所示:“警告:由于Python的AST编译器中栈深度限制,使用足够大/复杂的字符串可能会导致Python解释器崩溃。” 事实上,通过精心构造的堆栈攻击可能会运行任意代码,尽管据我所知,目前没有人建立公共概念证明。 - abarnert
4
@sqp_125,如果它是一个常规列表,那么您不需要解析任何内容吗? - ForceBru
2
文档(2021年版)指出:“这可以用于安全地评估包含Python值的字符串,这些值来自不受信任的来源,而无需自己解析这些值。它不能评估任意复杂的表达式,例如涉及运算符或索引的表达式。” - sebieire
显示剩余3条评论

247

json 模块是处理字符串化字典列表的更好解决方案。使用 json.loads(your_data) 函数将其转换为列表。

>>> import json
>>> x = '[ "A","B","C" , " D"]'
>>> json.loads(x)
['A', 'B', 'C', ' D']

同样地

>>> x = '[ "A","B","C" , {"D":"E"}]'
>>> json.loads(x)
['A', 'B', 'C', {'D': 'E'}]

31
在我的情况下,这适用于整数但不适用于字符串,因为每个字符串都使用单引号而不是双引号,唉。 - Paul Kenjora
30
根据@PaulKenjora的评论,它适用于'["a","b"]'但不适用于"['a','b']" - Skippy le Grand Gourou
5
在我的情况下,我必须将初始字符串中的单引号替换为双引号以确保它正常工作。.replace('\'', '"') 但我确信该字符串内部的数据不包含任何关键的单/双引号,这些引号会影响最终结果。 - Eugene Chabanov
如果用户只应输入数字列表,我认为这是最安全的方法,以防止恶意意图的用户。 - Muhammad Yasirroni
ast.literal_eval方法更加通用。例如,JSON无法处理字符串的b前缀,因为它不识别单独的bytes类型。JSON还要求字符串使用双引号。 - Karl Knechtel

115

eval 是危险的,你不应该执行用户输入。

如果你有2.6或更新版本,使用 ast 来代替 eval:

>>> import ast
>>> ast.literal_eval('["A","B" ,"C" ," D"]')
["A", "B", "C", " D"]

有了那个之后,strip字符串。

如果你使用的是较旧版本的Python,则可以通过简单的正则表达式实现非常接近所需的结果:

>>> x='[  "A",  " B", "C","D "]'
>>> re.findall(r'"\s*([^"]*?)\s*"', x)
['A', 'B', 'C', 'D']

这个方法不如用ast模块的解决方案好,例如它不能正确处理字符串中转义的引号。但它简单,不需要危险的eval操作,如果你使用旧版本的Python而没有ast模块的话,这个方法可能足够好。


请问你为什么说“eval很危险,不应该执行用户输入”?我正在使用3.6版本。 - Aaryan Dewan
2
@AaryanDewan 如果您直接使用 eval,它将评估任何有效的Python表达式,这可能是危险的。literal_eval通过仅评估Python文字结构来解决此问题:字符串、数字、元组、列表、字典、布尔值和None。 - Abhishek Menon

29

受到上面一些使用基本Python包的答案的启发,我比较了几个方法的性能(使用Python 3.7.3):

方法1:ast

import ast

list(map(str.strip, ast.literal_eval(u'[ "A","B","C" , " D"]')))
# ['A', 'B', 'C', 'D']

import timeit
timeit.timeit(stmt="list(map(str.strip, ast.literal_eval(u'[ \"A\",\"B\",\"C\" , \" D\"]')))", setup='import ast', number=100000)
# 1.292875313000195

方法二:json

import json
list(map(str.strip, json.loads(u'[ "A","B","C" , " D"]')))
# ['A', 'B', 'C', 'D']

import timeit
timeit.timeit(stmt="list(map(str.strip, json.loads(u'[ \"A\",\"B\",\"C\" , \" D\"]')))", setup='import json', number=100000)
# 0.27833264000014424

方法三:无需导入

list(map(str.strip, u'[ "A","B","C" , " D"]'.strip('][').replace('"', '').split(',')))
# ['A', 'B', 'C', 'D']

import timeit
timeit.timeit(stmt="list(map(str.strip, u'[ \"A\",\"B\",\"C\" , \" D\"]'.strip('][').replace('\"', '').split(',')))", number=100000)
# 0.12935059100027502

看到我认为可读性最差的方法却表现最佳,我感到失望......选择最易读的选项时需要考虑权衡利弊......对于我使用Python的工作负载类型,我通常更重视可读性而不是稍微更高效的选项,但这通常取决于具体情况。


1
在编程中,“'[ "A","B","C" , " D"]'”前面有一个“u”的特定原因吗? - Is_this_my_username
手动方法简单而不强大,完成的工作较少,因此速度更快并不令人惊讶。它无法处理字符串中的转义序列或不同类型的引号。(JSON 方法要求双引号,但可以处理转义序列。)它还只能处理一个平面字符串列表;其他方法可以处理复杂的嵌套数据结构。 - Karl Knechtel

28

有一个快速的解决方案:

x = eval('[ "A","B","C" , " D"]')

可以通过以下方式删除列表元素中的不需要的空格:

x = [x.strip() for x in eval('[ "A","B","C" , " D"]')]

1
这将仍然保留引号内的空格。 - tosh
35
这是一份针对任意代码执行的公开邀请,请不要这样做或类似行为,除非你绝对确定输入始终是100%可信的。 - Nicholas Knight
1
我可以采用这个建议,因为我知道我的数据始终会以那种格式存在,并且这是一个数据处理工作。 - Manish Ranjan

20
import ast
l = ast.literal_eval('[ "A","B","C" , " D"]')
l = [i.strip() for i in l]

16
如果只是一维列表,则可以在不导入任何内容的情况下完成:
>>> x = u'[ "A","B","C" , " D"]'
>>> ls = x.strip('[]').replace('"', '').replace(' ', '').split(',')
>>> ls
['A', 'B', 'C', 'D']

16
注意:如果列表中的任何字符串之间有逗号,这可能具有潜在的危险。 - Hassan Kamal
如果您的字符串列表是一个列表的列表,则此方法将无法正常工作。 - crypdick

12

大多数基本用例,包括原始问题中的用例,无需导入任何内容或进行评估即可在一行代码中完成。

一行代码实现

l_x = [i.strip() for i in x[1:-1].replace('"',"").split(',')]

解释

x = '[ "A","B","C" , " D"]'
# String indexing to eliminate the brackets.
# Replace, as split will otherwise retain the quotes in the returned list
# Split to convert to a list
l_x = x[1:-1].replace('"',"").split(',')

输出:

for i in range(0, len(l_x)):
    print(l_x[i])
# vvvv output vvvvv
'''
 A
B
C
  D
'''
print(type(l_x)) # out: class 'list'
print(len(l_x)) # out: 4

你可以使用列表推导式按需解析和清理此列表。

l_x = [i.strip() for i in l_x] # list comprehension to clean up
for i in range(0, len(l_x)):
    print(l_x[i])
# vvvvv output vvvvv
'''
A
B
C
D
'''

嵌套列表

如果你有嵌套列表,情况就会变得有点烦人。如果不使用正则表达式(这将简化替换过程),并且假设你想返回一个扁平化的列表(而Python之禅说扁平比嵌套好):

x = '[ "A","B","C" , " D", ["E","F","G"]]'
l_x = x[1:-1].split(',')
l_x = [i
    .replace(']', '')
    .replace('[', '')
    .replace('"', '')
    .strip() for i in l_x
]
# returns ['A', 'B', 'C', 'D', 'E', 'F', 'G']

如果需要保留嵌套列表,就会变得有些复杂,但是可以通过正则表达式和列表推导式来完成:

import re

x = '[ "A","B","C" , " D", "["E","F","G"]","Z", "Y", "["H","I","J"]", "K", "L"]'
# Clean it up so the regular expression is simpler
x = x.replace('"', '').replace(' ', '')
# Look ahead for the bracketed text that signifies nested list
l_x = re.split(r',(?=\[[A-Za-z0-9\',]+\])|(?<=\]),', x[1:-1])
print(l_x)
# Flatten and split the non nested list items
l_x0 = [item for items in l_x for item in items.split(',') if not '[' in items]
# Convert the nested lists to lists
l_x1 = [
    i[1:-1].split(',') for i in l_x if '[' in i
]
# Add the two lists
l_x = l_x0 + l_x1

这个最后的解决方案适用于任何以字符串形式存储的列表,无论是否嵌套。

请注意,该方法无法很好地处理空列表。您输入 '[]',却得到了 ['']。如果您正在解析数据框中的列,则可能会出现问题。除此之外,这是一个不错的解决方案! - Ari Anisfeld
列表推导式似乎比 x.strip('[]').replace('"', '').split(',') 慢。 可能是因为strip操作重复了len(x)次而不是1次,并且创建了两个列表而不是一个(split()返回的一个和推导式返回的一个)。 - Banane

10
你可以做到这件事。
**
x = '[ "A","B","C" , " D"]'
print(eval(x))

尽管这不是一个安全的方法,但最佳答案是被接受的。 回答发布时没有意识到 eval 的危险。


3
在该主题的几个地方不推荐使用eval,因为它将简单地运行输入的任何代码,存在安全风险。此外,这也是一个重复回答。 - born_naked

6
假设您的所有输入都是列表,并且输入中的双引号实际上并不重要,这可以通过简单的正则表达式替换来完成。它有点Perl-y,但它非常有效。请注意,输出现在是Unicode字符串列表,您没有指定需要它,但考虑到Unicode输入似乎是有意义的。
import re
x = u'[ "A","B","C" , " D"]'
junkers = re.compile('[[" \]]')
result = junkers.sub('', x).split(',')
print result
--->  [u'A', u'B', u'C', u'D']

变量junkers包含一个已编译的正则表达式(为了提高速度),用]作为必需字符需要一些反斜杠技巧。 re.sub将所有这些字符替换为空,并在逗号处分割结果字符串。 请注意,这也会从条目内部删除空格u'["oh no"]' ---> [u'ohno']。如果这不是您想要的,请升级正则表达式。

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