Como converter representação de string da lista em uma lista?

532

Eu queria saber qual é a maneira mais simples de converter uma stringlista como a seguinte em um list:

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

Mesmo que o usuário coloque espaços entre as vírgulas e espaços dentro das aspas. Eu preciso lidar com isso também para:

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

em Python.

Sei que posso retirar espaços com strip()e split()usando o operador split e verificar se não há alfabetos. Mas o código estava ficando muito desajeitado. Existe uma função rápida que eu não conheço?

harijay
fonte
4
O que você está realmente tentando realizar? Há provavelmente uma maneira muito melhor do que tentar converter Python lista de sintaxe em uma lista real ...
Nicholas Cavaleiro
1
Qual versão do Python você está usando?
22630 Mark Byers
2
@ Nicholas Knight: Estou tentando lidar com a entrada do usuário em um aplicativo herdado, onde todas as listas foram inseridas como listas unicode com parênteses quadrados. @ Mark Byers, estou usando python 2.6 de modo a abordagem ast.literal funciona melhor
harijay

Respostas:

769
>>> import ast
>>> x = u'[ "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 :

Com ast.literal_eval, você pode avaliar com segurança um nó de expressão ou uma sequência que contém uma expressão Python. A cadeia ou nó fornecido pode consistir apenas nas seguintes estruturas literais do Python: cadeias, números, tuplas, listas, dictos, booleanos e Nenhum.

Comunidade
fonte
6
Por comentário abaixo, isso é perigoso, pois simplesmente executa qualquer python na string. Portanto, se alguém fizer uma chamada para excluir tudo o que estiver lá dentro, ele será feliz.
Paul Kenjora
16
@PaulKenjora: Você está pensando eval, não ast.literal_eval.
User2357112 suporta Monica
19
ast.literal_evalé mais seguro que eval, mas na verdade não é seguro . Como as versões recentes dos documentos explicam: "Aviso É possível travar o interpretador Python com uma sequência suficientemente grande / complexa devido às limitações de profundidade da pilha no compilador AST do Python." De fato, pode ser possível executar código arbitrário por meio de um cuidadoso ataque de quebra de pilha, embora, até onde eu saiba, ninguém construa uma prova pública de conceito para isso.
abarnert
Bem, mas o que fazer se a lista não tiver aspas? por exemplo [4 de B, 1 de G]
sqp_125
84

O jsonmódulo é uma solução melhor sempre que houver uma lista estrita de dicionários. A json.loads(your_data)função pode ser usada para convertê-lo em uma lista.

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

similarmente

>>> x = u'[ "A","B","C" , {"D":"E"}]'
>>> json.loads(x)
[u'A', u'B', u'C', {u'D': u'E'}]
Ryan
fonte
no entanto, eu não quero a lista retornada no formato unicode. mas parece que mesmo que eu remova u '' da string, ele ainda trata os dados como unicode.
Mansoor Akram
7
Isso funciona para ints, mas não para strings no meu caso, porque cada string é simples e não dupla, suspiro.
Paul Kenjora
4
De acordo com o comentário de @ PaulKenjora, ele funciona para, '["a","b"]'mas não para "['a','b']".
Skippy le Grand Gourou
83

A evalé perigoso - você não deve executar a entrada do usuário.

Se você possui 2.6 ou mais recente, use ast em vez de eval:

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

Depois de ter isso, stripas cordas.

Se você está em uma versão mais antiga do Python, pode se aproximar muito do que deseja com uma simples expressão regular:

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

Isso não é tão bom quanto a solução ast, por exemplo, ele não lida corretamente com aspas escapadas em strings. Mas é simples, não envolve uma avaliação perigosa e pode ser suficiente para o seu objetivo se você estiver em um Python mais antigo sem ast.

Mark Byers
fonte
Você poderia, por favor, me dizer o porquê de você dizer: " evalÉ perigoso - você não deve executar a entrada do usuário". Eu estou usando 3.6
Aaryan Dewan
1
@AaryanDewan, se você usar evaldiretamente, ele avaliará qualquer expressão python válida, o que é potencialmente perigoso. literal_evalresolve esse problema avaliando apenas estruturas literais do Python: seqüências de caracteres, números, tuplas, listas, dictos, booleanos e Nenhum.
Abhishek Menon
14
import ast
l = ast.literal_eval('[ "A","B","C" , " D"]')
l = [i.strip() for i in l]
tosh
fonte
10

Existe uma solução rápida:

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

Os espaços em branco indesejados nos elementos da lista podem ser removidos desta maneira:

x = [x.strip() for x in eval('[ "A","B","C" , " D"]')]
Alexei Sholik
fonte
isso ainda preservar os espaços dentro das aspas
tosh
17
Este é um convite aberto à execução arbitrária de código, NUNCA faça isso ou algo parecido, a menos que você saiba com absoluta certeza que a entrada sempre será 100% confiável.
Nicholas Knight
1
Eu poderia usar essa sugestão porque sabia que meus dados sempre estariam nesse formato e que era um trabalho de processamento de dados.
Manish Ranjan
9

Inspirado em algumas das respostas acima que funcionam com pacotes python básicos, comparei o desempenho de alguns (usando o Python 3.7.3):

Método 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

Método 2: 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

Método 3: sem importação

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

Fiquei desapontado ao ver o que considerava o método com a pior legibilidade, o método com o melhor desempenho ... existem trocas a serem consideradas ao escolher a opção mais legível ... para o tipo de carga de trabalho que eu uso python normalmente legibilidade do valor sobre uma opção um pouco mais eficiente, mas, como sempre, depende.

kinzleb
fonte
9

Se for apenas uma lista unidimensional, isso pode ser feito sem importar nada:

>>> x = u'[ "A","B","C" , " D"]'
>>> ls = x.strip('[]').replace('"', '').replace(' ', '').split(',')
>>> ls
['A', 'B', 'C', 'D']
ruohola
fonte
8
Nota de precaução: isso pode ser perigoso se qualquer uma das seqüências de caracteres dentro da lista tiver uma vírgula no meio.
Hassan Kamal
Isso não funcionará se a sua lista de cadeias de caracteres for uma lista de listas
crypdick 19/03
@crypdick Bom ponto, acrescentou uma observação sobre isso :)
ruohola
6

Supondo que todas as suas entradas sejam listas e que as aspas duplas na entrada realmente não importam, isso pode ser feito com uma simples substituição de regexp. É um pouco pervertido, mas funciona como um encanto. Observe também que a saída agora é uma lista de cadeias unicode, você não especificou que precisava disso, mas parece fazer sentido dada a entrada 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']

A variável junkers contém um regexp compilado (para velocidade) de todos os caracteres que não queremos, usando] como um caractere necessário alguns truques de barra invertida. O re.sub substitui todos esses caracteres por nada e dividimos a sequência resultante pelas vírgulas.

Observe que isso também remove espaços das entradas internas u '["oh não"]' ---> [u'ohno ']. Se não é o que você queria, o regexp precisa ser aprimorado um pouco.

dirkjot
fonte
4

Se você souber que suas listas contêm apenas seqüências de caracteres entre aspas, este exemplo de pyparsing fornecerá sua lista de cadeias de caracteres com strip (mesmo preservando o Unicode-ness original).

>>> from pyparsing import *
>>> x =u'[ "A","B","C" , " D"]'
>>> LBR,RBR = map(Suppress,"[]")
>>> qs = quotedString.setParseAction(removeQuotes, lambda t: t[0].strip())
>>> qsList = LBR + delimitedList(qs) + RBR
>>> print qsList.parseString(x).asList()
[u'A', u'B', u'C', u'D']

Se suas listas podem ter mais tipos de dados ou até mesmo listas dentro de listas, você precisará de uma gramática mais completa - como esta no wiki pyparsing, que lida com tuplas, listas, ints, flutuadores e seqüências de caracteres citadas. Trabalhará com versões do Python de volta para 2.4.

PaulMcG
fonte
você gostaria de saber como usar "parseString (). asList ()", se eu tiver esse tipo de string: '["A", "B", "C", ["D"]]' ', como você afirmaram que a pyparsing também pode fazer isso. mas não parece ter encontrado o caminho certo para fazê-lo.
Mansoor Akram
"Se suas listas podem ter mais tipos de dados ou até mesmo listas dentro de listas, você precisará de uma gramática mais completa" - consulte o link que forneci na minha resposta para um analisador que manipulará listas aninhadas e vários outros tipos de dados.
PaulMcG
Pyparsing não está mais hospedado no wikispaces. O parsePythonValue.pyexemplo agora está no GitHub em github.com/pyparsing/pyparsing/blob/master/examples/…
PaulMcG
1

Para completar ainda mais a resposta de @Ryan usando json, uma função muito conveniente para converter unicode é a postada aqui: https://stackoverflow.com/a/13105359/7599285

ex com aspas duplas ou simples:

>print byteify(json.loads(u'[ "A","B","C" , " D"]')
>print byteify(json.loads(u"[ 'A','B','C' , ' D']".replace('\'','"')))
['A', 'B', 'C', ' D']
['A', 'B', 'C', ' D']
CptHwK
fonte
0

Eu gostaria de fornecer uma solução de padrões mais intuitiva com o regex. A função abaixo usa como entrada uma lista estratificada contendo seqüências arbitrárias.

Explicação passo a passo: Você remove todos os espaços em branco, bracketing e value_separators (desde que eles não façam parte dos valores que você deseja extrair, caso contrário, torne o regex mais complexo). Em seguida, divida a sequência limpa entre aspas simples ou duplas e obtenha os valores não vazios (ou valores indexados ímpares, independentemente da preferência).

def parse_strlist(sl):
import re
clean = re.sub("[\[\],\s]","",sl)
splitted = re.split("[\'\"]",clean)
values_only = [s for s in splitted if s != '']
return values_only

testample : "['21'," foo "'6', '0'," A "]"

Jordy Van Landeghem
fonte
0

e com python puro - não importando nenhuma biblioteca

[x for x in  x.split('[')[1].split(']')[0].split('"')[1:-1] if x not in[',',' , ',', ']]
Ioannis Nasios
fonte
0

Você pode encontrar esse problema ao lidar com dados raspados armazenados como Pandas DataFrame.

Essa solução funciona como charme se a lista de valores estiver presente como texto .

def textToList(hashtags):
    return hashtags.strip('[]').replace('\'', '').replace(' ', '').split(',')

hashtags = "[ 'A','B','C' , ' D']"
hashtags = textToList(hashtags)

Output: ['A', 'B', 'C', 'D']

Nenhuma biblioteca externa necessária.

dobydx
fonte
-1

Então, seguindo todas as respostas, decidi cronometrar os métodos mais comuns:

from time import time
import re
import json


my_str = str(list(range(19)))
print(my_str)

reps = 100000

start = time()
for i in range(0, reps):
    re.findall("\w+", my_str)
print("Regex method:\t", (time() - start) / reps)

start = time()
for i in range(0, reps):
    json.loads(my_str)
print("json method:\t", (time() - start) / reps)

start = time()
for i in range(0, reps):
    ast.literal_eval(my_str)
print("ast method:\t\t", (time() - start) / reps)

start = time()
for i in range(0, reps):
    [n.strip() for n in my_str]
print("strip method:\t", (time() - start) / reps)



    regex method:    6.391477584838867e-07
    json method:     2.535374164581299e-06
    ast method:      2.4425282478332518e-05
    strip method:    4.983267784118653e-06

Então, no final, o regex vence!

passa
fonte
-1

você pode salvar o arquivo .strip () fcn cortando apenas o primeiro e o último caracteres da representação de string da lista (veja a terceira linha abaixo)

>>> mylist=[1,2,3,4,5,'baloney','alfalfa']
>>> strlist=str(mylist)
['1', ' 2', ' 3', ' 4', ' 5', " 'baloney'", " 'alfalfa'"]
>>> mylistfromstring=(strlist[1:-1].split(', '))
>>> mylistfromstring[3]
'4'
>>> for entry in mylistfromstring:
...     print(entry)
...     type(entry)
... 
1
<class 'str'>
2
<class 'str'>
3
<class 'str'>
4
<class 'str'>
5
<class 'str'>
'baloney'
<class 'str'>
'alfalfa'
<class 'str'>
JCMontalbano
fonte