Як видалити елемент зі списку, якщо він існує?


258

Я отримую new_tagз текстового поля форми з полями self.response.get("new_tag")та selected_tagsз поля прапорця с

self.response.get_all("selected_tags")

Я поєдную їх так:

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

( f1.striplistце функція, яка позбавляє пробілів всередині рядків у списку.)

Але у випадку, який tag_listпорожній (нові теги не вводяться), але є деякі selected_tags, new_tag_listмістить порожній рядок " ".

Наприклад, із logging.info:

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

Як я можу позбутися порожнього рядка?

Якщо в списку є порожній рядок:

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

Але якщо порожнього рядка немає:

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

Але це дає:

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

Чому це відбувається і як я працюю навколо цього?

Відповіді:


718

1) Майже англійський стиль:

Перевірте наявність за допомогою inоператора, а потім застосуйте removeметод.

if thing in some_list: some_list.remove(thing)

removeМетод буде видалити тільки перше входження thing, щоб видалити всі входження , які можна використовувати whileзамість if.

while thing in some_list: some_list.remove(thing)    
  • Досить простий, напевно, мій вибір. Для невеликих списків (не вдається протистояти одній вкладиші)

2) Duck типізованих , ЕСПЦ стиль:

Це ставлення стрілянини - перше - запитання - остання - поширене в Python. Замість того, щоб заздалегідь перевірити, чи об'єкт підходить, просто виконайте операцію та вловіть відповідні винятки:

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

Звичайно, другий, окрім пункту у наведеному вище прикладі, є не лише сумнівним гумором, але й зовсім непотрібним (суть полягала в тому, щоб проілюструвати типи качок для людей, не знайомих з цією концепцією).

Якщо ви очікуєте кількох випадків:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • трохи детально для цього конкретного випадку використання, але дуже ідіоматичне в Python.
  • це краще, ніж №1
  • PEP 463 запропонував коротший синтаксис для спробу / за винятком простого використання, який був би корисним тут, але він не був затверджений.

Однак, використовуючи контекстний менеджер suppress () contextmanager (введений у python 3.4), наведений вище код може бути спрощений до цього:

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

Знову ж таки, якщо ви очікуєте кількох випадків:

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

3) Функціональний стиль:

Близько тисяча дев'ятсот дев'яносто три, Python є lambda, reduce(), filter()і map(), люб'язно Лисп хакера , який пропустив їх і представили робочі патчі *. Ви можете використовувати filterдля видалення елементів зі списку:

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

Існує ярлик, який може бути корисним для вашої справи: якщо ви хочете відфільтрувати порожні елементи (насправді елементи, де bool(item) == False, наприклад None, нуль, порожні рядки або інші порожні колекції), ви можете передавати None як перший аргумент:

cleaned_list = filter(None, some_list)
  • [оновлення] : в Python 2.x, який filter(function, iterable)використовується як еквівалент [item for item in iterable if function(item)](або [item for item in iterable if item]якщо перший аргумент None); в Python 3.x, це зараз еквівалентно (item for item in iterable if function(item)). Тонка різниця полягає в тому, що фільтр, який використовується для повернення списку, зараз він працює як генераторний вираз - це нормально, якщо ви лише перебираєте очищений список і відкидаєте його, але якщо вам справді потрібен список, вам потрібно вкласти filter()дзвінок з list()конструктором.
  • * Ці конструкції з ароматом Ліспі вважаються трохи чужими в Python. Близько 2005 року Гвідо навіть говорив про випаданняfilter - разом із супутниками mapта reduce(вони ще не пішли, але їх reduceперенесли у модуль фунікуолів , на що варто звернути увагу, якщо вам подобаються функції високого порядку ).

4) Математичний стиль:

Зрозуміння списків стало кращим стилем для маніпулювання списком у Python з моменту впровадження у версії 2.0 PEP 202 . Обгрунтуванням цього є , що опису списки забезпечують більш короткий спосіб створення списків в ситуаціях , де map()і filter()буде в даний час використовуються і / або вкладені цикли.

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

Генераторні вирази були введені у версії 2.4 PEP 289 . Вираз генератора краще для ситуацій, коли вам насправді не потрібно (або хочете) мати повний список, створений в пам'яті - наприклад, коли ви просто хочете переглядати елементи один за одним. Якщо ви переглядаєте лише список, ви можете вважати вираз генератора як ліниве оцінювання списку:

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

Примітки

  1. ви можете використовувати оператор нерівності !=замість is not( різниця важлива )
  2. для критиків методів, що передбачають копію списку: всупереч поширеній думці, генераторні вирази не завжди ефективніші, ніж списки розуміння - будь ласка, проаналізуйте, перш ніж скаржитися

3
Чи можу я запропонувати пропустити обробку AttributeError у (2)? Це відволікає увагу і не обробляється в інших розділах (або інших частинах цього ж розділу). Гірше, хтось може скопіювати цей код, не розуміючи, що вони надто агресивно пригнічують винятки. Оригінальне запитання передбачає перелік, відповідь також повинна.
Джейсон Р. Кумбс

1
Супер вичерпна відповідь! Чудово, що він розділився на різні розділи за "Стилем". Дякую!
halloleo

Що найшвидше?
Шешанк С.

12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

Зауважте, що це видалить із списку лише один екземпляр порожнього рядка (як і ваш код). Чи може ваш список містити більше одного?


5

Якщо indexрядок, який ValueErrorви шукаєте, не знайде, він кидає те, що ви бачите. Або спіймайте ValueError:

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

або використовувати find, що в такому випадку повертає -1.

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"

Чи є атрибут list () списку? Я отримую:>>> 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'
Зейнель

2
Підхід Time Pietscker remove()є набагато більш прямим: він прямо показує, що призначений для коду (насправді немає необхідності в проміжному індексі i).
Ерік О Лебігот

1
@Zeynel ні, він повинен бути у кожному Python, див. Docs.python.org/library/string.html#string.find . Але, як зазначає EOL, просто використовувати видалення - це краще.
фігаг

4

Додайте цю відповідь для повноти, хоча вона може бути використана лише за певних умов.

Якщо у вас дуже великі списки, видалення з кінця списку дозволяє уникнути внутрішніх процесів CPython memmoveдля ситуацій, коли ви можете повторно замовити список. Це дає збільшення продуктивності для видалення з кінця списку, оскільки його не потрібно буде memmove кожному елементу після того, як ви видаляєте - назад один крок (1) .
Для одноразових видалень різниця в продуктивності може бути прийнятною, але якщо у вас великий список і вам потрібно видалити багато елементів - ви, ймовірно, помітите хіт ефективності.

Хоча, правда, у цих випадках пошук повного списку, ймовірно, може бути і вузьким місцем продуктивності, якщо тільки пункти в основному не знаходяться внизу списку.

Цей метод може бути використаний для більш ефективного видалення до
тих пір, поки переупорядкування списку є прийнятним. (2)

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

Ви можете уникнути помилки, якщо її itemнемає в списку.

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. Хоча я тестував це з CPython, його цілком ймовірно більшість / всі інші реалізації Python використовують масив для внутрішнього зберігання списків. Тому, якщо вони не використовують складну структуру даних, розроблену для ефективного перегляду розмірів списку, вони, ймовірно, мають однакові характеристики продуктивності.

Простий спосіб перевірити це, порівняйте різницю швидкостей від видалення з передньої частини списку та видалення останнього елемента:

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

З:

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

(дає порядок різниці швидкостей за величиною, коли другий приклад швидший із CPython та PyPy).

  1. У цьому випадку ви можете розглянути можливість використання set, особливо якщо список не призначений для зберігання дублікатів.
    На практиці, можливо, вам знадобиться зберігати змінні дані, які неможливо додати до set. Також перевірте на btree's, чи можна замовити дані.

3

Еек, не робіть нічого складного:)

Просто filter()ваші теги. bool()повертає Falseпорожні рядки, тому замість

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

ви повинні написати

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

а ще краще, введіть цю логіку всередину, striplist()щоб вона не повертала порожні рядки в першу чергу.


Дякую! Усі добрі відповіді, але я думаю, я буду цим користуватися. Це моя striplistфункція, як я включаю ваше рішення: def striplist (l): "" "знімає пробіли від рядків у списку l" "" return ([x.strip () для x in l])
Zeynel

1
@Zeynel: впевнений. Ви можете або помістити тест всередині вашого списку розуміння , як це: [x.strip() for x in l if x.strip()]або використовувати Python вбудований mapі filterфункції , як це: filter(bool, map(str.strip, l)). Якщо ви хочете , щоб перевірити його, оцінити це в інтерактивному перекладача: filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter

Фільтр має ярлик для цього випадку (оцінка елемента в булевому контексті): достатньо використовувати Noneзамість boolпершого аргументу.
Пауло Скардін

2

Ось ще один підхід з одного лайнера:

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

Він не створює копію списку, не робить кілька проходів через список, не потребує додаткової обробки винятків і повертає відповідні об’єкти або None, якщо не відповідає. Єдине питання полягає в тому, що він робить довге твердження.

Взагалі, шукаючи однолінійне рішення, яке не викидає винятків, наступний () - це шлях, оскільки це одна з небагатьох функцій Python, яка підтримує аргумент за замовчуванням.


1

Все, що вам потрібно зробити - це це

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

але у цього методу є проблема. Вам потрібно щось поставити в окрім місця, тому я знайшов це:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")

3
Не слід перезаписувати вбудований список . І перетворення в рядок у другому фрагменті не потрібно.
Роберт Каспарі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.