Как удалить элемент в списке, если он существует?

259

Я получаю 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

Почему это происходит, и как мне обойти это?

Zeynel
источник

Ответы:

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) Тип утка , стиль EAFP :

Такое отношение «стреляй первым - задавай вопросы - последнее» распространено в 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 предложил более короткий синтаксис для try / кроме простого использования, который был бы здесь полезен, но не был одобрен.

Однако с помощью contextmanb contextlib's suppress () (представлен в python 3.4) приведенный выше код можно упростить до следующего:

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

Опять же, если вы ожидаете многократного появления вещи:

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

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

Около 1993, 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()конструктором.
  • * Эти ароматизированные конструкции Lispy считаются немного чужими в Python. Примерно в 2005 году Гвидо даже говорил об отбрасыванииfilter - вместе со своими компаньонами mapи reduce(они еще не ушли, но reduceбыли перенесены в модуль functools , который стоит посмотреть, если вам нравятся функции высокого порядка ).

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
Супер исчерпывающий ответ! Здорово, что он разделен на разные разделы с помощью «Стиля». Спасибо!
Привет,
Какой самый быстрый, хотя?
Шешанк С.
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"
phihag
источник
Является ли find () атрибутом списка? Я получаю:>>> 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, простое использование remove - это еще лучше.
Phihag
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, можно ли заказать данные.
ideasman42
источник
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()чтобы она не возвращала пустые строки.

dfichter
источник
Спасибо! Все хорошие ответы, но я думаю, что буду использовать это. Это моя striplistфункция, как мне включить ваше решение: def striplist (l): "" "удаляет пробелы из строк в списке l" "" return ([x.strip () для x в 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, если совпадения нет. Единственная проблема заключается в том, что это делает для длинного заявления.

В общем, при поиске однострочного решения, которое не вызывает исключений, next () - это путь, поскольку это одна из немногих функций 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")
SollyBunny
источник
3
Вы не должны перезаписывать встроенный список . И преобразование в строку не требуется во втором фрагменте.
Роберт Каспари