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?


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.


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

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.


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]

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

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.


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

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.


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.


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

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 ”]„


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

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.


-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!


-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'>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.