Jak usunąć element z listy, jeśli istnieje?

258

Otrzymuję new_tagz pola tekstowego formularza za pomocą self.response.get("new_tag")i selected_tagsz pól wyboru za pomocą

self.response.get_all("selected_tags")

Łączę je w następujący sposób:

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

( f1.striplistjest funkcją, która usuwa białe spacje wewnątrz ciągów na liście).

Jednak w przypadku, gdy tag_listjest pusta (brak nowych znaczniki są wprowadzone), ale istnieją pewne selected_tags, new_tag_listzawiera pusty ciąg " ".

Na przykład z logging.info:

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

Jak pozbyć się pustego ciągu?

Jeśli na liście jest pusty ciąg:

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

Ale jeśli nie ma pustego ciągu:

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

Ale to daje:

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

Dlaczego tak się dzieje i jak mam to obejść?

Zeynel
źródło

Odpowiedzi:

718

1) Prawie angielski styl:

Sprawdź obecność za pomocą inoperatora, a następnie zastosuj removemetodę.

if thing in some_list: some_list.remove(thing)

removeMetoda usunie tylko pierwsze wystąpienie thing, w celu usunięcia wszystkich wystąpień można użyć whilezamiast if.

while thing in some_list: some_list.remove(thing)    
  • Dość proste, prawdopodobnie mój wybór. Dla małych list (nie mogę się oprzeć jednowarstwowym)

2) Typ kaczki , styl EAFP :

Ta postawa „najpierw strzelaj, zadawaj pytania, ostatnia” jest powszechna w Pythonie. Zamiast wcześniej przetestować, czy obiekt jest odpowiedni, po prostu wykonaj operację i wychwyć odpowiednie wyjątki:

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

Oczywiście druga klauzula z wyjątkiem powyższego przykładu ma nie tylko wątpliwy humor, ale jest całkowicie niepotrzebna (chodziło o zilustrowanie pisania kaczką osobom, które nie znają tego pojęcia).

Jeśli oczekujesz wielu wystąpień rzeczy:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • trochę verbose dla tego konkretnego przypadku użycia, ale bardzo idiomatyczny w Pythonie.
  • to działa lepiej niż # 1
  • PEP 463 zaproponował krótszą składnię dla try / oprócz prostego użycia, które byłoby przydatne tutaj, ale nie został zatwierdzony.

Jednak za pomocą menedżera kontekstu suppress () kontekstowego (wprowadzonego w Pythonie 3.4) powyższy kod można uprościć do tego:

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

Ponownie, jeśli oczekujesz wielu wystąpień rzeczy:

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

3) Styl funkcjonalny:

Około 1993, Python got lambda, reduce(), filter()i map(), dzięki uprzejmości Lisp hakerów, którzy je pominąć i złożone łaty roboczych *. Możesz użyć filterdo usunięcia elementów z listy:

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

Istnieje skrót, który może być przydatny w twoim przypadku: jeśli chcesz odfiltrować puste elementy (w rzeczywistości elementy, w których bool(item) == Falsenp. NoneZero, puste ciągi znaków lub inne puste kolekcje), możesz przekazać None jako pierwszy argument:

cleaned_list = filter(None, some_list)
  • [aktualizacja] : w Pythonie 2.x, filter(function, iterable)był równoważny [item for item in iterable if function(item)](lub [item for item in iterable if item]jeśli pierwszy argument jest None); w Pythonie 3.x jest teraz równoważne z (item for item in iterable if function(item)). Subtelna różnica polega na tym, że filtr użyty do zwrócenia listy, teraz działa jak wyrażenie generatora - jest to OK, jeśli iterujesz tylko po oczyszczonej liście i odrzucasz ją, ale jeśli naprawdę potrzebujesz listy, musisz dołączyć filter()wywołanie z list()konstruktorem.
  • * Te konstrukcje o smaku Lispy są w Pythonie uważane za trochę obcych. Około 2005 roku Guido mówił nawet o upuszczeniufilter - wraz z towarzyszami mapi reduce(jeszcze ich nie ma, ale reducezostał przeniesiony do modułu funools , który warto sprawdzić, jeśli lubisz funkcje wyższego rzędu ).

4) Styl matematyczny:

Rozumienie list stało się preferowanym stylem do manipulowania listami w Pythonie od czasu wprowadzenia go w wersji 2.0 przez PEP 202 . Uzasadnieniem jest to, że Ułatwienia Lista zapewnić bardziej zwięzły sposób tworzenia list w sytuacjach, w których map()i filter()będzie stosowane obecnie i / lub zagnieżdżone pętle.

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

Wyrażenia generatora zostały wprowadzone w wersji 2.4 przez PEP 289 . Wyrażenie generatora jest lepsze w sytuacjach, w których tak naprawdę nie potrzebujesz (lub nie chcesz) mieć pełnej listy utworzonej w pamięci - na przykład gdy chcesz po prostu iterować elementy pojedynczo. Jeśli iterujesz tylko po liście, możesz pomyśleć o wyrażeniu generatora jako leniwym zrozumieniu listy:

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

Notatki

  1. możesz !=zamiast tego użyć operatora nierówności is not( różnica jest ważna )
  2. dla krytyków metod sugerujących kopię listy: wbrew powszechnemu przekonaniu, wyrażenia generatora nie zawsze są bardziej wydajne niż listy - prosimy o profil przed złożeniem skargi
Paulo Scardine
źródło
3
Czy mogę zasugerować pominięcie obsługi AttributeError w (2)? Rozprasza to uwagę i nie jest obsługiwane w innych sekcjach (ani w innych częściach tej samej sekcji). Co gorsza, ktoś może skopiować ten kod, nie zdając sobie sprawy, że zbyt agresywnie tłumi wyjątki. Oryginalne pytanie zakłada listę, odpowiedź również powinna.
Jason R. Coombs
1
Super kompleksowa odpowiedź! Wspaniale jest podzielić go na różne sekcje według „Stylu”. Dzięki!
halloleo
Który jest najszybszy?
Sheshank S.
12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

Zauważ, że spowoduje to usunięcie tylko jednego wystąpienia pustego ciągu z listy (tak jak zrobiłby to Twój kod). Czy twoja lista może zawierać więcej niż jeden?

Tim Pietzcker
źródło
5

Jeśli indexnie znajdzie szukanego ciągu, rzuca to, ValueErrorco widzisz. Albo złap ValueError:

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

lub użyj find, która zwraca -1 w tym przypadku.

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"
phihag
źródło
Czy find () jest atrybutem listy? Dostaję:>>> 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
Podejście Time Pietsckera remove()jest znacznie bardziej bezpośrednie: bezpośrednio pokazuje, co ma zrobić kod (w rzeczywistości nie jest potrzebny indeks pośredni i).
Eric O Lebigot,
1
@Zeynel nie, powinno być w każdym Pythonie, patrz docs.python.org/library/string.html#string.find . Ale jak wskazał EOL, po prostu usunięcie jest o wiele lepsze.
phihag
4

Dodanie tej odpowiedzi jest kompletne, ale można ją wykorzystać tylko pod pewnymi warunkami.

Jeśli masz bardzo duże listy, usunięcie z końca listy pozwala uniknąć konieczności stosowania wewnętrznych elementów CPython memmovew sytuacjach, w których możesz ponownie zamówić listę. Daje to wzrost wydajności do usunięcia z końca listy, ponieważ nie będzie musiał memmove każdego elementu po tym, który usuwasz - cofnij się o krok (1) .
W przypadku jednorazowego usunięcia różnica w wydajności może być do zaakceptowania, ale jeśli masz dużą listę i musisz usunąć wiele elementów - prawdopodobnie zauważysz spadek wydajności.

Chociaż wprawdzie w takich przypadkach przeszukiwanie pełnej listy może być również wąskim gardłem wydajności, chyba że elementy znajdują się głównie na początku listy.

Metodę tę można wykorzystać do wydajniejszego usuwania,
o ile dopuszczalne jest ponowne uporządkowanie listy. (2)

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

Możesz uniknąć błędu, gdy itemnie ma go na liście.

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. Podczas gdy testowałem to za pomocą CPython, najprawdopodobniej większość / wszystkie inne implementacje Pythona używają tablicy do wewnętrznego przechowywania list. Jeśli więc nie użyją wyrafinowanej struktury danych zaprojektowanej do wydajnego zmieniania rozmiaru listy, prawdopodobnie mają tę samą charakterystykę wydajności.

Prostym sposobem na sprawdzenie tego jest porównanie różnicy prędkości od usunięcia z początku listy z usunięciem ostatniego elementu:

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

Z:

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

(daje rząd różnicy prędkości rzędu wielkości, gdy drugi przykład jest szybszy z CPython i PyPy).

  1. W takim przypadku możesz rozważyć użycie a set, zwłaszcza jeśli lista nie jest przeznaczona do przechowywania duplikatów.
    W praktyce może być jednak konieczne przechowywanie zmiennych danych, których nie można dodać do pliku set. Sprawdź także na btree, czy dane można zamówić.
ideasman42
źródło
3

Eek, nie rób nic tak skomplikowanego:)

Tylko filter()twoje tagi. bool()zwraca Falsepuste ciągi, więc zamiast

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

powinieneś napisać

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

lub jeszcze lepiej, umieść tę logikę w środku, striplist()aby nie zwracała pustych ciągów.

dfichter
źródło
Dzięki! Wszystkie dobre odpowiedzi, ale myślę, że będę z tego korzystać. To jest moja striplistfunkcja, jak włączyć twoje rozwiązanie: def striplist (l): "" "usuwa białe spacje z ciągów znaków na liście l" "" return ([x.strip () dla x w l])
Zeynel
1
@Zeynel: pewnie. Można też umieścić test wewnątrz listy zrozumieniem tak: [x.strip() for x in l if x.strip()]albo użyć Python wbudowanej mapi filterfunkcje tak: filter(bool, map(str.strip, l)). Jeśli chcesz przetestować go, oceniać to w interaktywnym tłumacza: filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter
Filtr ma skrót do tego przypadku (ocena elementu w kontekście boolowskim): wystarczy użyć Nonezamiast boolpierwszego argumentu.
Paulo Scardine
2

Oto inne podejście liniowe:

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

Nie tworzy kopii listy, nie wykonuje wielu przejść przez listę, nie wymaga dodatkowej obsługi wyjątków i zwraca dopasowany obiekt lub Brak, jeśli nie ma dopasowania. Jedynym problemem jest to, że jest to długa wypowiedź.

Ogólnie rzecz biorąc, gdy szukasz rozwiązania jednowierszowego, które nie zgłasza wyjątków, najlepszym rozwiązaniem jest next (), ponieważ jest to jedna z niewielu funkcji Pythona, która obsługuje domyślny argument.

Dane White
źródło
1

Wszystko, co musisz zrobić, to to

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

ale ta metoda ma problem. Musisz umieścić coś w innym miejscu, więc znalazłem to:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")
SollyBunny
źródło
3
Nie należy zastępować wbudowanej listy . A konwersja na ciąg nie jest potrzebna w drugim fragmencie.
Robert Caspary