Jak przekonwertować ciąg reprezentujący listę na listę?

530

Zastanawiałem się, jaki jest najprostszy sposób na przekonwertowanie stringlisty w następujący sposób list:

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

Nawet w przypadku, gdy użytkownik wstawi spacje między przecinkami i spacje w cudzysłowie. Muszę sobie z tym poradzić również, aby:

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

w Pythonie.

Wiem, że mogę usuwać spacje za pomocą operatora podziału strip()i split()korzystać z niego, a także sprawdzać, czy nie występują alfabety. Ale kod stawał się bardzo niezgrabny. Czy jest jakaś szybka funkcja, której nie jestem świadomy?

harijay
źródło
4
Co tak naprawdę próbujesz osiągnąć? Prawdopodobnie istnieje o wiele lepszy sposób niż próba konwersji składni listy Pythona na rzeczywistą listę ...
Nicholas Knight
1
Jakiej wersji Pythona używasz?
Mark Byers,
2
@Nicholas Knight: Próbuję obsłużyć dane wejściowe użytkownika w starszej aplikacji, w której wszystkie listy zostały wprowadzone jako listy Unicode z nawiasami kwadratowymi. @Mark Byers, używam Pythona 2.6, więc podejście ast.literal działa najlepiej
harijay

Odpowiedzi:

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 :

Za pomocą ast.literal_eval możesz bezpiecznie ocenić węzeł wyrażenia lub ciąg znaków zawierający wyrażenie w języku Python. Podany ciąg znaków lub węzeł może składać się wyłącznie z następujących struktur literału Pythona: ciągów, liczb, krotek, list, dykt, boolanów i Brak.

Społeczność
źródło
6
W komentarzu poniżej jest to niebezpieczne, ponieważ po prostu uruchamia dowolny python w ciągu. Więc jeśli ktoś zadzwoni, aby usunąć wszystko, co tam jest, na szczęście to zrobi.
Paul Kenjora,
16
@PaulKenjora: Myślisz eval, nie ast.literal_eval.
user2357112 obsługuje Monikę
19
ast.literal_evaljest bezpieczniejszy niż eval, ale tak naprawdę nie jest bezpieczny . Jak wyjaśniają najnowsze wersje dokumentacji : „Ostrzeżenie Możliwe jest zawieszenie interpretera Pythona na wystarczająco dużym / złożonym łańcuchu ze względu na ograniczenia głębokości stosu w kompilatorze AST Pythona”. W rzeczywistości może być możliwe uruchomienie dowolnego kodu poprzez ostrożny atak niszczący stos, chociaż o ile wiem, nikt nie buduje publicznego dowodu na to.
abarnert
No ale co zrobić, jeśli na liście nie ma cudzysłowów? np. [4 z B, 1 z G]
sqp_125
84

jsonModuł jest lepszym rozwiązaniem, gdy istnieje stringified lista słowników. Za pomocą tej json.loads(your_data)funkcji można przekształcić ją w listę.

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

podobnie

>>> x = u'[ "A","B","C" , {"D":"E"}]'
>>> json.loads(x)
[u'A', u'B', u'C', {u'D': u'E'}]
Ryan
źródło
jednak nie chcę zwracanej listy w formacie Unicode. ale wydaje się, że nawet jeśli usunę u '' z ciągu, nadal traktuje dane jako Unicode.
Mansoor Akram
7
Działa to w przypadku liczb całkowitych, ale nie w przypadku ciągów znaków w moim przypadku, ponieważ każdy ciąg jest pojedynczy, a nie podwójny, westchnienie.
Paul Kenjora,
4
Zgodnie z komentarzem @ PaulKenjora działa, '["a","b"]'ale nie działa "['a','b']".
Skippy le Grand Gourou,
83

Jest evalto niebezpieczne - nie należy wykonywać danych wprowadzanych przez użytkownika.

Jeśli masz wersję 2.6 lub nowszą, użyj ast zamiast eval:

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

Kiedy już to zrobisz, stripstruny.

Jeśli korzystasz ze starszej wersji Pythona, możesz zbliżyć się do tego, co chcesz, za pomocą prostego wyrażenia regularnego:

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

To nie jest tak dobre, jak rozwiązanie ast, na przykład nie obsługuje poprawnie cudzysłowów w ciągach. Ale to proste, nie wymaga niebezpiecznej ewaluacji i może być wystarczające dla twojego celu, jeśli używasz starszego Pythona bez astmy.

Mark Byers
źródło
Czy możesz mi powiedzieć, dlaczego powiedziałeś „To evaljest niebezpieczne - nie powinieneś wprowadzać danych użytkownika”. Używam 3.6
Aaryan Dewan
1
@AaryanDewan, jeśli użyjesz evalbezpośrednio, oceni każde poprawne wyrażenie python, które jest potencjalnie niebezpieczne. literal_evalrozwiązuje ten problem, oceniając jedynie literalne struktury Pythona: ciągi, liczby, krotki, listy, dykta, logiczne i Brak.
Abhishek Menon
14
import ast
l = ast.literal_eval('[ "A","B","C" , " D"]')
l = [i.strip() for i in l]
bzdury
źródło
10

Istnieje szybkie rozwiązanie:

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

Niepożądane białe znaki w elementach listy można usunąć w ten sposób:

x = [x.strip() for x in eval('[ "A","B","C" , " D"]')]
Alexei Sholik
źródło
to wciąż zachowałoby spacje w cudzysłowie
tosh
17
Jest to otwarte zaproszenie do wykonania dowolnego kodu, NIGDY nie rób tego ani nic podobnego, chyba że wiesz z absolutną pewnością, że dane wejściowe zawsze będą w 100% zaufane.
Nicholas Knight
1
Mogłem skorzystać z tej sugestii, ponieważ wiedziałem, że moje dane będą zawsze w tym formacie i były przetwarzane.
Manish Ranjan
9

Zainspirowany niektórymi powyższymi odpowiedziami, które działają z podstawowymi pakietami Pythona, porównałem wydajność kilku (używając Pythona 3.7.3):

Metoda 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

Metoda 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

Metoda 3: bez importu

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

Byłem rozczarowany, gdy zobaczyłem, że to, co uważałem za metodę o najgorszej czytelności, to metoda o najlepszej wydajności ... są pewne kompromisy, które należy wziąć pod uwagę, wybierając najbardziej czytelną opcję ... dla rodzajów obciążeń, w których zwykle używam Pythona czytelność wartości w porównaniu z nieco bardziej wydajną opcją, ale jak zwykle to zależy.

kinzleb
źródło
9

Jeśli jest to tylko lista jednowymiarowa, można to zrobić bez importowania czegokolwiek:

>>> x = u'[ "A","B","C" , " D"]'
>>> ls = x.strip('[]').replace('"', '').replace(' ', '').split(',')
>>> ls
['A', 'B', 'C', 'D']
ruohola
źródło
8
Uwaga: może to być niebezpieczne, jeśli dowolny z ciągów na liście zawiera przecinek.
Hassan Kamal
To nie zadziała, jeśli twoja lista ciągów jest listą
crypdick
@crypdick Dobra uwaga, dodałem notatkę na ten temat :)
ruohola
6

Zakładając, że wszystkie dane wejściowe są listami i że podwójne cudzysłowy w danych wejściowych nie mają znaczenia, można tego dokonać za pomocą prostej zamiany wyrażenia regularnego. To trochę perl-y, ale działa jak urok. Zauważ też, że wyjście jest teraz listą ciągów Unicode, nie określiłeś, że jest to potrzebne, ale wydaje się mieć sens biorąc pod uwagę dane wejściowe 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']

Zmienna junkers zawiera skompilowane wyrażenie regularne (dla szybkości) wszystkich znaków, których nie chcemy, użycie] jako znaku wymagało pewnych sztuczek z odwrotnym ukośnikiem. Re.sub zastępuje wszystkie te znaki niczym, a wynikowy ciąg dzielimy na przecinki.

Zauważ, że to również usuwa spacje z wewnętrznych wpisów u '[„oh no”] ”---> [u'ohno']. Jeśli nie tego chciałeś, regexp musi zostać nieco ulepszony.

dirkjot
źródło
4

Jeśli wiesz, że twoje listy zawierają tylko ciągi cytowane, ten przykład parsowania da ci listę pasowanych ciągów (nawet zachowując oryginalną Unicode).

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

Jeśli twoje listy mogą zawierać więcej typów danych, a nawet zawierać listy w obrębie list, będziesz potrzebować pełniejszej gramatyki - takiej jak ta na wiki pyparsing, która będzie obsługiwać krotki, listy, liczby całkowite, zmiennoprzecinkowe i ciągi cytowane. Będzie działał z wersjami Pythona od wersji 2.4.

PaulMcG
źródło
czy dałbyś mi znać, jak używać „parseString (). asList ()”, jeśli mam tego rodzaju ciąg znaków: „[” A ”,„ B ”,„ C ”, [„ D ”]], tak jak stwierdzili, że pyparsowanie również może to zrobić. ale nie wydaje się, że znalazłeś właściwy sposób, aby to zrobić.
Mansoor Akram
„Jeśli twoje listy mogą zawierać więcej typów danych lub nawet zawierać listy w obrębie list, będziesz potrzebować pełniejszej gramatyki” - zobacz link podany w mojej odpowiedzi dla parsera, który będzie obsługiwał listy zagnieżdżone, i różnych innych typów danych.
PaulMcG
Pyparsing nie jest już hostowany na wiki. parsePythonValue.pyPrzykładem jest teraz na GitHub na github.com/pyparsing/pyparsing/blob/master/examples/...
PaulMcG
1

Aby uzupełnić odpowiedź @Ryan za pomocą jsona, jedną bardzo wygodną funkcją konwersji Unicode jest ta zamieszczona tutaj: https://stackoverflow.com/a/13105359/7599285

np. z podwójnymi lub pojedynczymi cytatami:

>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
źródło
0

Chciałbym zapewnić bardziej intuicyjne rozwiązanie wzorcowania z regex. Poniższa funkcja przyjmuje jako dane wejściowe listę łańcuchową zawierającą dowolne ciągi znaków.

Wyjaśnienie krokowe: Usuwasz wszystkie spacje, braketing i separatory wartości (pod warunkiem, że nie są one częścią wartości, które chcesz wyodrębnić, w przeciwnym razie wyrażenie regularne będzie bardziej złożone). Następnie dzielisz oczyszczony ciąg na pojedyncze lub podwójne cudzysłowy i bierzesz niepuste wartości (lub nieparzyste wartości indeksowane, niezależnie od preferencji).

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
źródło
0

oraz z czystym pythonem - bez importowania żadnych bibliotek

[x for x in  x.split('[')[1].split(']')[0].split('"')[1:-1] if x not in[',',' , ',', ']]
Ioannis Nasios
źródło
0

Możesz napotkać taki problem, mając do czynienia ze zeskrobanymi danymi przechowywanymi jako Pandas DataFrame.

To rozwiązanie działa jak urok, jeśli lista wartości jest obecna jako tekst .

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

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

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

Nie wymaga biblioteki zewnętrznej.

dobydx
źródło
-1

Tak więc, podążając za wszystkimi odpowiedziami, postanowiłem ustalić najbardziej popularne metody:

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

W końcu regex wygrywa!

przechodzi
źródło
-1

możesz zaoszczędzić sobie .strip () fcn, po prostu odcinając pierwszy i ostatni znak z reprezentacji ciągu listy (patrz trzeci wiersz poniżej)

>>> 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
źródło