Bagaimana cara menghapus item dalam daftar jika ada?

259

Saya mendapatkan new_tagdari bidang teks formulir dengan self.response.get("new_tag")dan selected_tagsdari bidang kotak centang dengan

self.response.get_all("selected_tags")

Saya menggabungkannya seperti ini:

tag_string = new_tag
new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

( f1.striplistadalah fungsi yang menghapus spasi di dalam string dalam daftar.)

Tetapi dalam kasus yang tag_listkosong (tidak ada tag baru dimasukkan) tetapi ada beberapa selected_tags, new_tag_listberisi string kosong " ".

Misalnya, dari logging.info:

new_tag
selected_tags[u'Hello', u'Cool', u'Glam']
new_tag_list[u'', u'Hello', u'Cool', u'Glam']

Bagaimana cara menyingkirkan string kosong?

Jika ada string kosong dalam daftar:

>>> s = [u'', u'Hello', u'Cool', u'Glam']
>>> i = s.index("")
>>> del s[i]
>>> s
[u'Hello', u'Cool', u'Glam']

Tetapi jika tidak ada string kosong:

>>> s = [u'Hello', u'Cool', u'Glam']
>>> if s.index(""):
        i = s.index("")
        del s[i]
    else:
        print "new_tag_list has no empty string"

Tapi ini memberi:

Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    if new_tag_list.index(""):
        ValueError: list.index(x): x not in list

Mengapa ini terjadi, dan bagaimana saya mengatasinya?

Zeynel
sumber

Jawaban:

718

1) Gaya yang hampir berbahasa Inggris:

Tes keberadaan menggunakan inoperator, lalu terapkan removemetode.

if thing in some_list: some_list.remove(thing)

The removeMetode akan menghapus hanya kejadian pertama thing, dalam rangka untuk menghapus semua kejadian yang dapat Anda gunakan whilebukan if.

while thing in some_list: some_list.remove(thing)    
  • Cukup sederhana, mungkin pilihan saya. Untuk daftar kecil (tidak bisa menolak satu kalimat)

2) Bebek yang diketik , gaya EAFP :

Sikap menembak-dulu-bertanya-terakhir-ini umum dalam Python. Alih-alih menguji terlebih dahulu jika objek tersebut cocok, lakukan saja operasi dan tangkap Pengecualian yang relevan:

try:
    some_list.remove(thing)
except ValueError:
    pass # or scream: thing not in some_list!
except AttributeError:
    call_security("some_list not quacking like a list!")

Tentu saja yang kedua kecuali klausa dalam contoh di atas tidak hanya humor yang dipertanyakan tetapi sama sekali tidak perlu (intinya adalah untuk menggambarkan mengetik bebek untuk orang-orang yang tidak akrab dengan konsep).

Jika Anda mengharapkan beberapa kejadian:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • sedikit verbose untuk kasus penggunaan khusus ini, tetapi sangat idiomatis dengan Python.
  • ini berkinerja lebih baik daripada # 1
  • PEP 463 mengusulkan sintaks yang lebih pendek untuk mencoba / kecuali penggunaan sederhana yang akan berguna di sini, tetapi itu tidak disetujui.

Namun, dengan penekan contextlib () contextmanager (diperkenalkan dengan python 3.4) kode di atas dapat disederhanakan menjadi:

with suppress(ValueError, AttributeError):
    some_list.remove(thing)

Sekali lagi, jika Anda mengharapkan beberapa kejadian:

with suppress(ValueError):
    while True:
        some_list.remove(thing)

3) Gaya fungsional:

Sekitar tahun 1993, Python mendapat lambda, reduce(), filter()dan map(), courtesy of Lisp hacker yang merindukan mereka dan patch bekerja diserahkan *. Anda dapat menggunakan filteruntuk menghapus elemen dari daftar:

is_not_thing = lambda x: x is not thing
cleaned_list = filter(is_not_thing, some_list)

Ada jalan pintas yang mungkin berguna untuk kasus Anda: jika Anda ingin menyaring item kosong (pada kenyataannya item di mana bool(item) == False, seperti None, nol, string kosong atau koleksi kosong lainnya), Anda dapat melewati Tidak ada sebagai argumen pertama:

cleaned_list = filter(None, some_list)
  • [pembaruan] : dengan Python 2.x, filter(function, iterable)digunakan untuk menjadi setara dengan [item for item in iterable if function(item)](atau [item for item in iterable if item]jika argumen pertama adalah None); di Python 3.x, sekarang setara dengan (item for item in iterable if function(item)). Perbedaannya adalah filter yang digunakan untuk mengembalikan daftar, sekarang berfungsi seperti ekspresi generator - ini OK jika Anda hanya mengulangi daftar yang sudah dibersihkan dan membuangnya, tetapi jika Anda benar-benar membutuhkan daftar, Anda harus menyertakan filter()panggilan dengan list()konstruktor.
  • * Konstruksi rasa Lispy ini dianggap sedikit asing dengan Python. Sekitar tahun 2005, Guido bahkan berbicara tentang menjatuhkanfilter - bersama dengan teman mapdan reduce(mereka belum pergi tetapi reducedipindahkan ke modul functools , yang layak dilihat jika Anda menyukai fungsi pesanan tinggi ).

4) Gaya matematika:

Pemahaman daftar menjadi gaya yang disukai untuk manipulasi daftar dengan Python sejak diperkenalkan pada versi 2.0 oleh PEP 202 . Alasan di balik itu adalah bahwa Daftar comprehensions menyediakan cara yang lebih ringkas untuk membuat daftar dalam situasi di mana map()dan filter()dan / atau loop bersarang saat ini akan digunakan.

cleaned_list = [ x for x in some_list if x is not thing ]

Ekspresi generator diperkenalkan dalam versi 2.4 oleh PEP 289 . Ekspresi generator lebih baik untuk situasi di mana Anda tidak benar-benar perlu (atau ingin) membuat daftar lengkap dalam memori - seperti ketika Anda hanya ingin mengulangi elemen satu per satu. Jika Anda hanya mengulangi daftar, Anda dapat menganggap ekspresi generator sebagai pemahaman daftar yang dievaluasi malas :

for item in (x for x in some_list if x is not thing):
    do_your_thing_with(item)

Catatan

  1. Anda mungkin ingin menggunakan operator ketimpangan !=alih-alih is not( perbedaannya penting )
  2. untuk kritik terhadap metode yang menyiratkan salinan daftar: bertentangan dengan kepercayaan populer, ekspresi generator tidak selalu lebih efisien daripada pemahaman daftar - silakan profil sebelum mengeluh
Paulo Scardine
sumber
3
Bolehkah saya menyarankan menghilangkan penanganan AttributeError di (2)? Ini mengganggu dan tidak ditangani di bagian lain (atau bagian lain dari bagian yang sama). Lebih buruk lagi, seseorang mungkin menyalin kode itu tanpa menyadari bahwa mereka terlalu agresif menekan pengecualian. Pertanyaan aslinya mengasumsikan daftar, jawabannya juga harus.
Jason R. Coombs
1
Jawaban super komprehensif! Sangat bagus untuk membaginya menjadi beberapa bagian dengan "Gaya". Terima kasih!
halloleo
Yang mana yang tercepat?
Sheshank S.
12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

Perhatikan bahwa ini hanya akan menghapus satu instance dari string kosong dari daftar Anda (seperti kode Anda juga akan). Bisakah daftar Anda memuat lebih dari satu?

Tim Pietzcker
sumber
5

Jika indextidak menemukan string yang dicari, itu melempar yang ValueErrorAnda lihat. Entah menangkap ValueError:

try:
    i = s.index("")
    del s[i]
except ValueError:
    print "new_tag_list has no empty string"

atau gunakan find, yang mengembalikan -1 dalam kasus itu.

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"
phihag
sumber
Apakah menemukan () atribut daftar? Saya mendapatkan:>>> s [u'Hello', u'Cool', u'Glam'] >>> i = s.find("") Traceback (most recent call last): File "<pyshell#42>", line 1, in <module> i = s.find("") AttributeError: 'list' object has no attribute 'find'
Zeynel
2
Pendekatan Time Pietscker remove()jauh lebih langsung: itu secara langsung menunjukkan apa yang harus dilakukan oleh kode (memang tidak perlu indeks perantara i).
Eric O Lebigot
1
@ Zeynel tidak, itu harus di setiap Python, lihat docs.python.org/library/string.html#string.find . Tapi seperti yang ditunjukkan EOL, cukup menggunakan remove adalah lebih baik.
phihag
4

Menambahkan jawaban ini untuk kelengkapan, meskipun hanya dapat digunakan dalam kondisi tertentu.

Jika Anda memiliki daftar yang sangat besar, menghapus dari akhir daftar menghindari keharusan internal CPython memmove, untuk situasi di mana Anda dapat memesan ulang daftar. Ini memberikan keuntungan kinerja untuk dihapus dari akhir daftar, karena tidak perlu memmove setiap item setelah Anda menghapus - kembali satu langkah (1) .
Untuk pemindahan satu kali saja, perbedaan kinerja mungkin dapat diterima, tetapi jika Anda memiliki daftar besar dan perlu menghapus banyak item - Anda mungkin akan melihat peningkatan kinerja.

Meskipun harus diakui, dalam kasus ini, melakukan pencarian daftar lengkap kemungkinan akan menjadi hambatan kinerja juga, kecuali item sebagian besar di bagian depan daftar.

Metode ini dapat digunakan untuk penghapusan yang lebih efisien,
selama pemesanan ulang daftar dapat diterima. (2)

def remove_unordered(ls, item):
    i = ls.index(item)
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()

Anda mungkin ingin menghindari kesalahan saat itemtidak ada dalam daftar.

def remove_unordered_test(ls, item):
    try:
        i = ls.index(item)
    except ValueError:
        return False
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()
    return True

  1. Sementara saya menguji ini dengan CPython, kemungkinan besar / semua implementasi Python lainnya menggunakan array untuk menyimpan daftar secara internal. Jadi kecuali mereka menggunakan struktur data canggih yang dirancang untuk mengubah ukuran daftar secara efisien, mereka cenderung memiliki karakteristik kinerja yang sama.

Cara sederhana untuk menguji ini, bandingkan perbedaan kecepatan dari menghapus dari depan daftar dengan menghapus elemen terakhir:

python -m timeit 'a = [0] * 100000' 'while a: a.remove(0)'

Dengan:

python -m timeit 'a = [0] * 100000' 'while a: a.pop()'

(memberikan urutan perbedaan kecepatan magnitudo di mana contoh kedua lebih cepat dengan CPython dan PyPy).

  1. Dalam hal ini Anda dapat mempertimbangkan menggunakan set, terutama jika daftar tersebut tidak dimaksudkan untuk menyimpan duplikat.
    Namun dalam praktiknya Anda mungkin perlu menyimpan data yang bisa berubah yang tidak dapat ditambahkan ke file set. Periksa juga btree's jika data dapat dipesan.
gagasanman42
sumber
3

Eek, jangan lakukan hal yang rumit :)

Hanya filter()tag Anda. bool()kembali Falseuntuk string kosong, jadi alih-alih

new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

kamu harus menulis

new_tag_list = filter(bool, f1.striplist(tag_string.split(",") + selected_tags))

atau lebih baik lagi, letakkan logika ini di dalam striplist()sehingga tidak mengembalikan string kosong di tempat pertama.

dfichter
sumber
Terima kasih! Semua jawaban bagus tapi saya pikir saya akan menggunakan ini. Ini adalah striplistfungsi saya , bagaimana saya memasukkan solusi Anda: def striplist (l): "" "strip whitespaces dari string dalam daftar l" "" return ([x.strip () untuk x in l])
Zeynel
1
@ Zeynel: tentu. Anda juga bisa menempatkan tes dalam daftar pemahaman Anda seperti ini: [x.strip() for x in l if x.strip()]atau menggunakan Python built-in mapdan filterfungsi seperti ini: filter(bool, map(str.strip, l)). Jika Anda ingin menguji itu, mengevaluasi ini di interpreter interaktif: filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter
Filter memiliki pintasan untuk kasus ini (mengevaluasi elemen dalam konteks Boolean): menggunakan Nonebukan booluntuk argumen pertama sudah cukup.
Paulo Scardine
2

Berikut ini pendekatan satu-liner lain untuk dibuang di sana:

next((some_list.pop(i) for i, l in enumerate(some_list) if l == thing), None)

Itu tidak membuat salinan daftar, tidak membuat beberapa melewati seluruh daftar, tidak memerlukan penanganan pengecualian tambahan, dan mengembalikan objek yang cocok atau Tidak ada jika tidak ada kecocokan. Satu-satunya masalah adalah itu membuat pernyataan panjang.

Secara umum, ketika mencari solusi satu-liner yang tidak membuang pengecualian, next () adalah cara untuk pergi, karena itu salah satu dari beberapa fungsi Python yang mendukung argumen default.

Dane White
sumber
1

Yang harus Anda lakukan adalah ini

list = ["a", "b", "c"]
    try:
        list.remove("a")
    except:
        print("meow")

tetapi metode itu memiliki masalah. Anda harus meletakkan sesuatu di tempat kecuali jadi saya menemukan ini:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")
SollyBunny
sumber
3
Anda tidak boleh menimpa daftar bawaan. Dan konversi ke string tidak diperlukan di cuplikan ke-2.
Robert Caspary