Удалить пустые строки из списка строк
Я хочу удалить все пустые строки из списка строк в Python.
Мой подход выглядит так:
while '' in str_list:
str_list.remove('')
Есть ли более питонический способ сделать это?
5 ответ(ов)
Функция filter
действительно имеет специальный параметр для этой цели:
filter(None, sequence)
Этот вызов отфильтрует все элементы, которые оцениваются как False
. Вам не нужно использовать никакие вызовы, такие как bool
, len
и так далее.
По производительности это аналогично map(bool, ...)
.
В данном примере мы сравниваем два способа удаления пустых строк из списка строк с использованием Python: метод join()
и filter()
.
Исходный список строк:
lstr = ['hello', '', ' ', 'world', ' ']
При использовании ' '.join(lstr).split()
мы получаем результат:
['hello', 'world']
Этот подход объединяет все элементы списка в одну строку, разделяя их пробелами, а затем разбивает строку обратно на список, удаляя все временные пробелы и пустые строки.
Сравнительно с этим, когда мы применяем filter(None, lstr)
, мы получаем:
['hello', ' ', 'world', ' ']
Этот метод удаляет только пустые строки (''
), оставляя строки с пробелами (' '
).
Теперь рассмотрим производительность. Мы можем провести тестирование времени выполнения для обоих методов, используя библиотеку timeit
.
from timeit import timeit
# Время выполнения join и split
time_join_split = timeit('" ".join(lstr).split()', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
print(time_join_split) # Примерный результат: 4.226747989654541
# Время выполнения filter
time_filter = timeit('filter(None, lstr)', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
print(time_filter) # Примерный результат: 3.0278358459472656
Хотя filter(None, lstr)
быстрее, он не удаляет пробелы как строки. Чтобы использовать filter()
для удаления строк с пробелами, нам нужно создать новый список с заменой пробелов:
# Время выполнения filter с удалением пробелов
time_filter_with_space_removal = timeit('filter(None, [l.replace(" ", "") for l in lstr])', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
print(time_filter_with_space_removal) # Примерный результат: 18.101892948150635
Таким образом, выводя результаты, можно заметить, что метод ' '.join(lstr).split()
более эффективен по времени, но filter()
в этом случае непрактичен, если требуется обработка строк с пробелами.
Обзор лучших решений:
1. Удаление пустых строк БЕЗ удаления пробелов:
То есть, строки, состоящие только из пробелов, сохраняются:
slist = list(filter(None, slist))
Плюсы:
- Непростое решение;
- Быстрое (см. среднее время на примерах ниже).
2. Удаление пустых строк после удаления лишних пробелов ...
2.a ... когда строки НЕ содержат пробелов между словами:
slist = ' '.join(slist).split()
Плюсы:
- Небольшой объем кода;
- Быстрое выполнение. (НО не самое быстрое для больших наборов данных из-за использования памяти, в противовес результатам @paolo-melchiorre)
2.b ... когда строки содержат пробелы между словами?
slist = list(filter(str.strip, slist))
Плюсы:
- Самое быстрое решение;
- Легкость понимания кода.
Тесты производительности на машине 2018 года:
## Создание тестовых данных
#
import random, string
nwords = 10000
maxlen = 30
null_ratio = 0.1
rnd = random.Random(0) # детерминированные результаты
words = [' ' * rnd.randint(0, maxlen)
if rnd.random() > (1 - null_ratio)
else
''.join(random.choices(string.ascii_letters, k=rnd.randint(0, maxlen)))
for _i in range(nwords)
]
## Тестирование функций
#
def nostrip_filter(slist):
return list(filter(None, slist))
def nostrip_comprehension(slist):
return [s for s in slist if s]
def strip_filter(slist):
return list(filter(str.strip, slist))
def strip_filter_map(slist):
return list(filter(None, map(str.strip, slist)))
def strip_filter_comprehension(slist): # затраты памяти
return list(filter(None, [s.strip() for s in slist]))
def strip_filter_generator(slist):
return list(filter(None, (s.strip() for s in slist)))
def strip_join_split(slist): # слова без(!) пробелов
return ' '.join(slist).split()
## Тесты производительности
#
%timeit nostrip_filter(words)
142 µs ± 16.8 µs за цикл (среднее ± стандартное отклонение из 7 запусков, 10000 циклов каждый)
%timeit nostrip_comprehension(words)
263 µs ± 19.1 µs за цикл (среднее ± стандартное отклонение из 7 запусков, 1000 циклов каждый)
%timeit strip_filter(words)
653 µs ± 37.5 µs за цикл (среднее ± стандартное отклонение из 7 запусков, 1000 циклов каждый)
%timeit strip_filter_map(words)
642 µs ± 36 µs за цикл (среднее ± стандартное отклонение из 7 запусков, 1000 циклов каждый)
%timeit strip_filter_comprehension(words)
693 µs ± 42.2 µs за цикл (среднее ± стандартное отклонение из 7 запусков, 1000 циклов каждый)
%timeit strip_filter_generator(words)
750 µs ± 28.6 µs за цикл (среднее ± стандартное отклонение из 7 запусков, 1000 циклов каждый)
%timeit strip_join_split(words)
796 µs ± 103 µs за цикл (среднее ± стандартное отклонение из 7 запусков, 1000 циклов каждый)
Ответ от @Ib33X действительно замечательный. Если вы хотите удалить все пустые строки после удаления пробелов, вам также нужно использовать метод strip()
. В противном случае вы получите пустую строку, если она содержит только пробелы. Например, строка " "
будет считаться допустимой. Таким образом, можно достичь желаемого результата с помощью следующего кода:
strings = ["first", "", "second ", " "]
[x.strip() for x in strings if x.strip()]
Результатом будет ["first", "second"]
.
Если вы хотите использовать метод filter
, это можно сделать так:
list(filter(lambda item: item.strip(), strings))
Это также вернёт тот же самый результат.
Вместо проверки на if x
, я бы использовал if x != ''
, чтобы просто исключить пустые строки. Вот так:
str_list = [x for x in str_list if x != '']
Это позволит сохранить элементы типа None
в вашем списке. Также, если в списке есть целые числа и 0
является одним из них, он тоже будет сохранён.
Например:
str_list = [None, '', 0, "Hi", '', "Hello"]
[x for x in str_list if x != '']
Этот код вернёт:
[None, 0, "Hi", "Hello"]
Таким образом, вы получите список, из которого удалены только пустые строки, а остальные элементы останутся нетронутыми.
Почему используется string.join(list), а не list.join(string)?
Как преобразовать строковое представление списка в список?
Как проверить, является ли строка подстрокой элементов в списке строк?
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе
Вывод строки в текстовый файл