Comment convertir une représentation sous forme de chaîne d'une liste en une liste?

532

Je me demandais quel était le moyen le plus simple de convertir une stringliste comme la suivante en un list:

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

Même dans le cas où l'utilisateur place des espaces entre les virgules et des espaces à l'intérieur des guillemets. Je dois également gérer cela pour:

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

en Python.

Je sais que je peux supprimer les espaces avec strip()et en split()utilisant l'opérateur de séparation et vérifier les non alphabets. Mais le code devenait très délicat. Y a-t-il une fonction rapide dont je ne suis pas au courant?

harijay
la source
4
Qu'essayez-vous réellement d'accomplir? Il y a probablement un bien meilleur moyen que d'essayer de convertir la syntaxe de liste Python en une liste réelle ...
Nicholas Knight
1
Quelle version de Python utilisez-vous?
Mark Byers
2
@Nicholas Knight: J'essaie de gérer les entrées des utilisateurs dans une application héritée où toutes les listes ont été entrées sous forme de listes unicode avec des parenthèses carrées. @Mark Byers, j'utilise python 2.6 donc l'approche ast.literal fonctionne mieux
harijay

Réponses:

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 :

Avec ast.literal_eval, vous pouvez évaluer en toute sécurité un nœud d'expression ou une chaîne contenant une expression Python. La chaîne ou le nœud fourni ne peut être composé que des structures littérales Python suivantes: chaînes, nombres, tuples, listes, dictés, booléens et Aucun.

Communauté
la source
6
Par commentaire ci-dessous, cela est dangereux car il exécute simplement le python dans la chaîne. Donc, si quelqu'un appelle pour supprimer tout ce qu'il contient, il le fera avec plaisir.
Paul Kenjora
16
@PaulKenjora: Vous pensez à eval, non ast.literal_eval.
user2357112 prend en charge Monica
19
ast.literal_evalest plus sûr que eval, mais ce n'est pas vraiment sûr . Comme l' expliquent les versions récentes des documents : "Avertissement Il est possible de bloquer l'interpréteur Python avec une chaîne suffisamment grande / complexe en raison des limitations de profondeur de pile dans le compilateur AST de Python." En fait, il peut être possible d'exécuter du code arbitraire via une attaque minutieuse de destruction de pile, bien que, pour autant que je sache, personne n'a construit une preuve de concept publique pour cela.
abarnert
Eh bien, que faire si la liste n'a pas de guillemets? par exemple [4 de B, 1 de G]
sqp_125
84

Le jsonmodule est une meilleure solution chaque fois qu'il y a une liste de dictionnaires stratifié . La json.loads(your_data)fonction peut être utilisée pour la convertir en liste.

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

De même

>>> x = u'[ "A","B","C" , {"D":"E"}]'
>>> json.loads(x)
[u'A', u'B', u'C', {u'D': u'E'}]
Ryan
la source
cependant je ne veux pas la liste retournée au format unicode. mais semble que même si je supprime u '' de la chaîne, il traite toujours les données comme unicode.
Mansoor Akram
7
Cela fonctionne pour les entiers mais pas pour les chaînes dans mon cas, car chaque chaîne est entre guillemets simples et non entre guillemets, soupir.
Paul Kenjora
4
Selon le commentaire de @ PaulKenjora, cela fonctionne pour '["a","b"]'mais pas pour "['a','b']".
Skippy le Grand Gourou
83

C'est evaldangereux - vous ne devez pas exécuter la saisie utilisateur.

Si vous avez 2.6 ou plus récent, utilisez ast au lieu de eval:

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

Une fois que vous avez cela, striples cordes.

Si vous utilisez une ancienne version de Python, vous pouvez être très proche de ce que vous voulez avec une simple expression régulière:

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

Ce n'est pas aussi bon que la solution ast, par exemple, il ne gère pas correctement les guillemets échappés dans les chaînes. Mais c'est simple, n'implique pas une évaluation dangereuse, et pourrait être assez bon pour votre objectif si vous êtes sur un Python plus ancien sans ast.

Mark Byers
la source
Pourriez-vous s'il vous plaît me dire pourquoi vous avez dit: «C'est evaldangereux - vous ne devriez pas exécuter la saisie utilisateur.»? J'utilise 3.6
Aaryan Dewan
1
@AaryanDewan si vous l'utilisez evaldirectement, il évaluera toute expression python valide, potentiellement dangereuse. literal_evalrésout ce problème en évaluant uniquement les structures littérales Python: chaînes, nombres, tuples, listes, dictés, booléens et Aucun.
Abhishek Menon
14
import ast
l = ast.literal_eval('[ "A","B","C" , " D"]')
l = [i.strip() for i in l]
tosh
la source
10

Il existe une solution rapide:

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

Les espaces blancs indésirables dans les éléments de la liste peuvent être supprimés de cette manière:

x = [x.strip() for x in eval('[ "A","B","C" , " D"]')]
Alexei Sholik
la source
cela conserverait toujours les espaces à l'intérieur des guillemets
tosh
17
Il s'agit d'une invitation ouverte à l'exécution de code arbitraire, ne faites JAMAIS cela ou quoi que ce soit d'autre à moins que vous ne sachiez avec une certitude absolue que l'entrée sera toujours 100% fiable.
Nicholas Knight,
1
Je pourrais utiliser cette suggestion parce que je savais que mes données seraient toujours dans ce format et étaient un travail de traitement de données.
Manish Ranjan
9

Inspiré de certaines des réponses ci-dessus qui fonctionnent avec les packages python de base, j'ai comparé les performances de quelques-uns (en utilisant Python 3.7.3):

Méthode 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éthode 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éthode 3: aucune importation

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

J'ai été déçu de voir que ce que je considérais comme la méthode avec la pire lisibilité était la méthode avec les meilleures performances ... il y a des compromis à considérer lors de l'utilisation de l'option la plus lisible ... pour le type de charges de travail que j'utilise python pour moi habituellement valeur de lecture sur une option légèrement plus performante, mais comme d'habitude, cela dépend.

kinzleb
la source
9

S'il ne s'agit que d'une liste unidimensionnelle, cela peut être fait sans rien importer:

>>> x = u'[ "A","B","C" , " D"]'
>>> ls = x.strip('[]').replace('"', '').replace(' ', '').split(',')
>>> ls
['A', 'B', 'C', 'D']
ruohola
la source
8
Mise en garde: cela peut être potentiellement dangereux si l'une des chaînes de la liste contient une virgule entre les deux.
Hassan Kamal
Cela ne fonctionnera pas si votre liste de chaînes est une liste de listes
crypdick
@crypdick Bon point, a ajouté une note à ce sujet :)
ruohola
6

En supposant que toutes vos entrées sont des listes et que les guillemets doubles dans l'entrée n'ont pas d'importance, cela peut être fait avec un simple regexp replace. C'est un peu perl-y mais fonctionne comme un charme. Notez également que la sortie est maintenant une liste de chaînes unicode, vous n'avez pas spécifié que vous en aviez besoin, mais cela semble logique étant donné l'entrée 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']

La variable junkers contient une expression rationnelle compilée (pour la vitesse) de tous les caractères que nous ne voulons pas, en utilisant] comme caractère, il a fallu quelques trucs antislash. Le re.sub remplace tous ces caractères par rien, et nous séparons la chaîne résultante par des virgules.

Notez que cela supprime également les espaces à l'intérieur des entrées u '["oh no"]' ---> [u'ohno ']. Si ce n'est pas ce que vous vouliez, l'expression rationnelle doit être un peu gonflée.

dirkjot
la source
4

Si vous savez que vos listes contiennent uniquement des chaînes entre guillemets, cet exemple de mise en page vous donnera votre liste de chaînes supprimées (même en préservant la Unicode d'origine).

>>> 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']

Si vos listes peuvent avoir plus de types de données, ou même contenir des listes dans des listes, alors vous aurez besoin d'une grammaire plus complète - comme celle-ci sur le wiki pyparsing, qui gérera les tuples, les listes, les entrées, les flottants et les chaînes entre guillemets. Fonctionnera avec les versions Python de retour à 2.4.

PaulMcG
la source
voudriez-vous me faire savoir comment utiliser "parseString (). asList ()", si j'ai ce type de chaîne: '["A", "B", "C", ["D"]]', comme vous ont déclaré que le pyparsing peut également le faire. mais o ne semble pas avoir trouvé la bonne façon de le faire.
Mansoor Akram
"Si vos listes peuvent avoir plus de types de données, ou même contenir des listes dans des listes, alors vous aurez besoin d'une grammaire plus complète" - veuillez consulter le lien que j'ai fourni dans ma réponse pour un analyseur qui gérera les listes imbriquées et divers autres types de données.
PaulMcG
Le Pyparsing n'est plus hébergé sur les wikispaces. L' parsePythonValue.pyexemple est maintenant sur GitHub à github.com/pyparsing/pyparsing/blob/master/examples/…
PaulMcG
1

Pour compléter la réponse de @Ryan en utilisant json, une fonction très pratique pour convertir unicode est celle publiée ici: https://stackoverflow.com/a/13105359/7599285

ex avec des guillemets doubles 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
la source
0

Je voudrais fournir une solution de modelage plus intuitive avec regex. La fonction ci-dessous prend en entrée une liste stringifiée contenant des chaînes arbitraires.

Explication pas à pas: vous supprimez tous les espaces blancs, les crochets et les valeurs_séparateurs (à condition qu'ils ne fassent pas partie des valeurs que vous souhaitez extraire, sinon rendre l'expression rationnelle plus complexe). Ensuite, vous divisez la chaîne nettoyée entre guillemets simples ou doubles et prenez les valeurs non vides (ou les valeurs indexées impaires, quelle que soit la préférence).

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

exemple de test : "['21'," foo "'6', '0'," A "]"

Jordy Van Landeghem
la source
0

et avec du python pur - ne pas importer de bibliothèques

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

Vous pouvez rencontrer un tel problème lorsque vous traitez des données récupérées stockées en tant que Pandas DataFrame.

Cette solution fonctionne comme un charme si la liste de valeurs est présente sous forme de texte .

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

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

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

Aucune bibliothèque externe requise.

dobydx
la source
-1

Donc, en suivant toutes les réponses, j'ai décidé de chronométrer les méthodes les plus courantes:

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

Donc, finalement, regex gagne!

passe
la source
-1

vous pouvez vous enregistrer le fcn .strip () en coupant simplement les premier et dernier caractères de la représentation sous forme de chaîne de la liste (voir la troisième ligne ci-dessous)

>>> 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
la source