Bagaimana cara mengubah representasi string dari daftar ke daftar?

532

Saya bertanya-tanya apa cara paling sederhana untuk mengubah stringdaftar seperti berikut ini menjadi list:

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

Bahkan jika pengguna menempatkan spasi di antara koma, dan spasi di dalam tanda kutip. Saya perlu mengatasinya juga untuk:

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

dalam Python.

Saya tahu saya bisa menghapus spasi dengan strip()dan split()menggunakan operator perpecahan dan memeriksa bukan huruf. Tetapi kode itu menjadi sangat kludgy. Apakah ada fungsi cepat yang tidak saya sadari?

harijay
sumber
4
Apa yang sebenarnya ingin Anda capai? Mungkin ada cara yang jauh lebih baik daripada mencoba untuk mengkonversi Python daftar sintaks ke dalam daftar yang sebenarnya ...
Nicholas Ksatria
1
Versi Python apa yang Anda gunakan?
Mark Byers
2
@Nicholas Knight: Saya mencoba menangani input pengguna dalam aplikasi lawas tempat semua daftar dimasukkan sebagai daftar unicode dengan tanda kurung persegi. @ Mark Byers, saya menggunakan python 2.6 sehingga pendekatan ast.literal bekerja dengan baik
harijay

Jawaban:

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 :

Dengan ast.literal_eval, Anda dapat dengan aman mengevaluasi simpul ekspresi atau string yang berisi ekspresi Python. String atau node yang disediakan hanya dapat terdiri dari struktur literal Python berikut: string, angka, tuple, daftar, dicts, booleans, dan None.

Komunitas
sumber
6
Per komentar di bawah, ini berbahaya karena hanya menjalankan python apa pun yang ada di string. Jadi, jika seseorang menelepon untuk menghapus semua yang ada di sana, itu dengan senang hati akan dilakukan.
Paul Kenjora
16
@ PaulKenjora: Kamu sedang memikirkan eval, bukan ast.literal_eval.
user2357112 mendukung Monica
19
ast.literal_evaladalah lebih aman daripada eval, tapi itu tidak benar-benar aman . Seperti yang dijelaskan oleh versi terbaru dokumen : "Peringatan Dimungkinkan untuk menabrak juru bahasa Python dengan string yang cukup besar / kompleks karena batasan kedalaman tumpukan pada kompiler AST Python." Pada kenyataannya, dimungkinkan untuk menjalankan kode arbitrer melalui serangan stack-smashing yang cermat, meskipun sejauh yang saya tahu tidak ada yang membangun bukti konsep publik untuk itu.
abarnert
Baiklah tapi apa yang harus dilakukan jika Daftar tidak memiliki tanda kutip? misalnya [4 dari B, 1 dari G]
sqp_125
84

The jsonmodul adalah solusi yang lebih baik setiap kali ada stringified daftar kamus. The json.loads(your_data)fungsi dapat digunakan untuk mengubahnya menjadi daftar.

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

Demikian pula

>>> x = u'[ "A","B","C" , {"D":"E"}]'
>>> json.loads(x)
[u'A', u'B', u'C', {u'D': u'E'}]
Ryan
sumber
Namun saya tidak ingin daftar yang dikembalikan dalam format unicode. tetapi sepertinya bahkan jika saya menghapus u '' dari string itu masih memperlakukan data sebagai unicode.
Mansoor Akram
7
Ini berfungsi untuk int tetapi tidak untuk string dalam kasus saya karena setiap string tunggal dikutip tidak dikutip ganda, desah.
Paul Kenjora
4
Sesuai komentar @ PaulKenjora, itu berfungsi '["a","b"]'tetapi tidak untuk "['a','b']".
Skippy le Grand Gourou
83

Ini evalberbahaya - Anda seharusnya tidak mengeksekusi input pengguna.

Jika Anda memiliki 2,6 atau lebih baru, gunakan ast bukan eval:

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

Setelah Anda memilikinya, stripsenarnya.

Jika Anda menggunakan versi Python yang lebih lama, Anda bisa mendekati apa yang Anda inginkan dengan ekspresi reguler sederhana:

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

Ini tidak sebagus solusi ast, misalnya ia tidak dengan benar menangani tanda kutip yang lolos dalam string. Tapi itu sederhana, tidak melibatkan eval berbahaya, dan mungkin cukup baik untuk tujuan Anda jika Anda menggunakan Python yang lebih tua tanpa ast.

Mark Byers
sumber
Bisakah Anda memberi tahu saya mengapa Anda mengatakan “Ini evalberbahaya - Anda tidak boleh mengeksekusi input pengguna.”? Saya menggunakan 3,6
Aaryan Dewan
1
@AaryanDewan jika Anda menggunakan evalsecara langsung, itu akan mengevaluasi ekspresi python yang valid, yang berpotensi berbahaya. literal_evalMemecahkan masalah ini dengan hanya mengevaluasi struktur literal Python: string, angka, tuple, daftar, dicts, boolean, dan Tidak ada.
Abhishek Menon
14
import ast
l = ast.literal_eval('[ "A","B","C" , " D"]')
l = [i.strip() for i in l]
omong kosong
sumber
10

Ada solusi cepat:

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

Ruang putih yang tidak diinginkan dalam elemen daftar dapat dihapus dengan cara ini:

x = [x.strip() for x in eval('[ "A","B","C" , " D"]')]
Alexei Sholik
sumber
ini masih akan melestarikan ruang di dalam tanda kutip
tosh
17
Ini adalah undangan terbuka untuk eksekusi kode arbitrer, JANGAN PERNAH melakukan ini atau yang seperti itu kecuali Anda tahu dengan pasti bahwa input akan selalu dipercaya 100%.
Nicholas Knight
1
Saya bisa menggunakan saran ini karena saya tahu data saya akan selalu dalam format itu dan merupakan pekerjaan pemrosesan data.
Manish Ranjan
9

Terinspirasi dari beberapa jawaban di atas yang berfungsi dengan paket python dasar saya membandingkan kinerja beberapa (menggunakan Python 3.7.3):

Metode 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

Metode 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

Metode 3: tidak ada impor

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

Saya kecewa melihat apa yang saya anggap metode dengan keterbacaan terburuk adalah metode dengan kinerja terbaik ... ada pengorbanan untuk dipertimbangkan ketika pergi dengan opsi yang paling mudah dibaca ... untuk jenis beban kerja yang saya gunakan python karena saya biasanya nilai keterbacaan atas opsi yang sedikit lebih banyak performan, tetapi seperti biasa itu tergantung.

kinzleb
sumber
9

Jika hanya daftar satu dimensi, ini dapat dilakukan tanpa mengimpor apa pun:

>>> x = u'[ "A","B","C" , " D"]'
>>> ls = x.strip('[]').replace('"', '').replace(' ', '').split(',')
>>> ls
['A', 'B', 'C', 'D']
ruohola
sumber
8
Catatan hati-hati: ini berpotensi berbahaya jika salah satu string di dalam daftar memiliki koma di antaranya.
Hassan Kamal
Ini tidak akan berfungsi jika daftar string Anda adalah daftar daftar
crypdick
@crypdick Poin bagus, menambahkan catatan tentang itu :)
ruohola
6

Dengan asumsi bahwa semua input Anda adalah daftar dan bahwa tanda kutip ganda pada input sebenarnya tidak masalah, ini dapat dilakukan dengan penggantian regexp sederhana. Ini agak perl-y tetapi bekerja seperti pesona. Perhatikan juga bahwa output sekarang adalah daftar string unicode, Anda tidak menentukan bahwa Anda membutuhkannya, tetapi tampaknya masuk akal jika diberikan input 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']

Variabel junker berisi regexp yang dikompilasi (untuk kecepatan) dari semua karakter yang tidak kita inginkan, menggunakan] sebagai karakter yang diperlukan beberapa tipuan backslash. Re.sub menggantikan semua karakter ini dengan tidak ada, dan kami membagi string yang dihasilkan di koma.

Perhatikan bahwa ini juga menghilangkan spasi dari entri dalam u '["oh tidak"]' ---> [u'ohno ']. Jika ini bukan yang Anda inginkan, regexp perlu ditingkatkan sedikit.

dirkjot
sumber
4

Jika Anda tahu bahwa daftar Anda hanya berisi string yang dikutip, contoh pyparsing ini akan memberi Anda daftar string yang dilucuti (bahkan mempertahankan Unicode-ness asli).

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

Jika daftar Anda dapat memiliki lebih banyak tipe data, atau bahkan berisi daftar di dalam daftar, maka Anda akan memerlukan tata bahasa yang lebih lengkap - seperti yang ada di wiki pyparsing ini, yang akan menangani tupel, daftar, int, float, dan string yang dikutip. Akan bekerja dengan versi Python kembali ke 2.4.

PaulMcG
sumber
tolong beri tahu saya cara menggunakan "parseString (). asList ()", jika saya memiliki string seperti ini: '["A", "B", "B", "C", ["D"]]', saat Anda telah menyatakan bahwa pyparsing dapat melakukannya juga. tetapi tampaknya Anda tidak menemukan cara yang tepat untuk melakukannya.
Mansoor Akram
"Jika daftar Anda dapat memiliki lebih banyak tipe data, atau bahkan berisi daftar dalam daftar, maka Anda akan memerlukan tata bahasa yang lebih lengkap" - silakan lihat tautan yang saya berikan dalam jawaban saya untuk parser yang akan menangani daftar bersarang, dan berbagai tipe data lainnya.
PaulMcG
Pyparsing tidak lagi di-host di wikispaces. The parsePythonValue.pycontoh adalah sekarang GitHub di github.com/pyparsing/pyparsing/blob/master/examples/...
PaulMcG
1

Untuk melengkapi jawaban @Ryan menggunakan json, satu fungsi yang sangat mudah untuk mengonversi unicode adalah yang diposting di sini: https://stackoverflow.com/a/13105359/7599285

ex dengan tanda kutip ganda atau tunggal:

>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
sumber
0

Saya ingin memberikan solusi pola yang lebih intuitif dengan regex. Fungsi di bawah ini mengambil input daftar string yang berisi string arbitrer.

Penjelasan bertahap: Anda menghapus semua spasi putih, tanda kurung, dan value_separator (asalkan itu bukan bagian dari nilai yang ingin Anda ekstrak, kalau tidak buat regex lebih kompleks). Kemudian Anda membagi string yang telah dibersihkan pada tanda kutip tunggal atau ganda dan mengambil nilai yang tidak kosong (atau nilai indeks ganjil, apa pun preferensi).

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
sumber
0

dan dengan python murni - tidak mengimpor perpustakaan apa pun

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

Anda dapat mengalami masalah seperti itu saat berurusan dengan data yang tergores yang disimpan sebagai Pandas DataFrame.

Solusi ini berfungsi seperti pesona jika daftar nilai hadir sebagai teks .

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

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

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

Tidak diperlukan perpustakaan eksternal.

dobydx
sumber
-1

Jadi, dengan mengikuti semua jawaban, saya memutuskan untuk menggunakan metode yang paling umum:

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

Jadi pada akhirnya regex menang!

lulus
sumber
-1

Anda dapat menyimpan sendiri .strip () fcn hanya dengan memotong karakter pertama dan terakhir dari representasi string dari daftar (lihat baris ketiga di bawah)

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