filter() в Python: как отфильтровать список
Функция filter в Python оставляет из коллекции только те элементы, для которых условие вернуло истину. В статье показаны вызов с обычной функцией и с lambda, фильтрация строк и словарей, особый режим filter(None, ...), а также случаи, когда удобнее генератор списка.

Содержание статьи
Синтаксис filter()
Функция встроенная, импортировать ничего не нужно. Она принимает два аргумента и возвращает итератор.
filter(function, iterable)functionвызывается для каждого элемента и должна вернуть значение, которое Python проверит на истинность. Вместо функции можно передатьNone.iterableэто любой перебираемый объект: список, кортеж, строка, множество, словарь, файл, генератор.
Элемент попадает в результат, если function(элемент) дает истинное значение. Порядок элементов сохраняется, сами элементы не изменяются. Если значения нужно преобразовать, а не отобрать, используется родственная функция map(), она разобрана в статье про zip и map.
Фильтрация с обычной функцией
Когда условие занимает несколько строк или пригодится в других местах программы, его оформляют отдельной функцией и передают в filter() по имени, без скобок.
def is_even(number):
return number % 2 == 0
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(is_even, numbers))
print(even_numbers) # [2, 4, 6, 8, 10]Функции не обязательно возвращать именно True или False. Подойдет любое значение: непустая строка и ненулевое число считаются истинными. Но явное булево значение делает код понятнее.
VOWELS = set("aeiou")
def is_vowel(letter):
return letter.lower() in VOWELS
letters = ['a', 'b', 'd', 'E', 'i', 'j', 'o']
print(tuple(filter(is_vowel, letters))) # ('a', 'E', 'i', 'o')Результат можно превратить в любую коллекцию: список, кортеж, множество. Для проверки вхождения выгоднее держать гласные во множестве: поиск в нем быстрее, чем в списке.
Условие через lambda
Короткие условия удобно записывать прямо в вызове анонимной функцией. Это самый частый вариант использования filter(). Если синтаксис lambda пока незнаком, загляните в статью про лямбда функции.
temps = [-3, 5, -1, 8, 0, 12]
positive = list(filter(lambda t: t > 0, temps))
print(positive) # [5, 8, 12]Отбор строк
Со строками часто фильтруют по длине, началу или содержимому. Методы строк вызываются внутри lambda как обычно.
files = ['report.pdf', 'photo.jpg', 'notes.txt', 'scan.PDF']
pdfs = list(filter(lambda f: f.lower().endswith('.pdf'), files))
print(pdfs) # ['report.pdf', 'scan.PDF']
words = ['кот', 'программа', 'дом', 'интерпретатор']
print(list(filter(lambda w: len(w) > 5, words)))
# ['программа', 'интерпретатор']Списки словарей и словари
Если элементы это словари, условие обращается к нужному ключу. Сам словарь при переборе отдает ключи, поэтому для фильтрации пар используют items() и собирают результат обратно через dict().
students = [
{'name': 'Анна', 'score': 85},
{'name': 'Борис', 'score': 52},
{'name': 'Вера', 'score': 78},
]
passed = filter(lambda s: s['score'] >= 60, students)
print([s['name'] for s in passed]) # ['Анна', 'Вера']
stock = {'яблоки': 12, 'груши': 0, 'сливы': 7}
available = dict(filter(lambda item: item[1] > 0, stock.items()))
print(available) # {'яблоки': 12, 'сливы': 7}Удаление пустых значений через filter(None)
Если первым аргументом передать None, функция оставит только истинные элементы. Это короткий способ очистить данные от пустых строк, нулей и None.
raw = [1, 'a', 0, False, True, '0', '', None, [], 3.5]
print(list(filter(None, raw))) # [1, 'a', True, '0', 3.5]| Ложные значения (удаляются) | Похожие, но истинные (остаются) |
|---|---|
0, 0.0, False | '0', -1 |
'' | ' ' (пробел) |
None | 'None' |
[], (), {}, set() | [0], [None] |
Будьте внимательны с числами: filter(None, ...) выбросит и законный ноль. Если нужно убрать только None, пишите условие явно: filter(lambda x: x is not None, data).
Результат это одноразовый итератор
В Python 3 filter() возвращает не список, а объект filter. Он ленивый: элементы проверяются только в момент, когда их запрашивают. Поэтому print(filter(...)) показывает что-то вроде <filter object at 0x...>.
evens = filter(lambda x: x % 2 == 0, range(10))
print(list(evens)) # [0, 2, 4, 6, 8]
print(list(evens)) # [] итератор уже исчерпан
first_big = next(filter(lambda x: x > 1000, range(10**9)))
print(first_big) # 1001Ленивость дает два следствия. Пройти по результату можно только один раз, для повторного использования его сохраняют в список. Зато можно фильтровать огромные или бесконечные последовательности и строки большого файла, не загружая все в память. Подробнее о таком поведении написано в статье про итераторы.
Что выбрать: filter() или генератор списка
Тот же отбор можно записать генератором списка. Выбор чаще зависит от читаемости, чем от скорости.
numbers = range(1, 11)
via_filter = list(filter(lambda n: n % 3 == 0, numbers))
via_comp = [n for n in numbers if n % 3 == 0]
print(via_filter == via_comp) # True| Ситуация | Что удобнее |
|---|---|
Готовая функция-условие: str.isdigit, is_valid | filter(str.isdigit, items) |
| Условие нужно записать на месте | Генератор списка |
| Нужно и отобрать, и преобразовать | [x * 2 for x in items if x > 0] |
| Большие данные без списка в памяти | filter() или выражение-генератор в круглых скобках |
Синтаксис и возможности второго варианта описаны в статье про list comprehension. Для обратной задачи, когда нужны элементы, не прошедшие проверку, в стандартной библиотеке есть itertools.filterfalse(). Описание обеих функций есть в документации Python.
Частые вопросы
Короткие ответы на вопросы, которые задают при знакомстве с filter().
Почему print(filter(...)) не показывает элементы?
Функция возвращает ленивый итератор, а не список. Оберните результат в list() или переберите его циклом for.
Меняет ли filter() исходный список?
Нет, исходная коллекция остается прежней. Результат нужно присвоить новой переменной или той же самой: items = list(filter(...)).
Можно ли передать в filter() несколько условий?
Функция принимает одно условие, но внутри него можно объединить проверки через and и or: filter(lambda x: x > 0 and x % 2 == 0, data). Другой вариант: вложить один filter() в другой.
Как отфильтровать строку и получить строку?
Отобранные символы собирают методом join(): ''.join(filter(str.isdigit, 'a1b2c3')) вернет '123'.