Сегодня рассказываем про ещё одну простую, но интересную возможность на Python — встроенную фильтрацию данных с помощью функции filter()
.
Введение в функцию filter()
Функция filter()
в Python — это встроенная возможность отобрать нужные значения из какого-то однородного набора данных по созданной программистом формуле. Если говорить формально, то эта функция работает с итерируемыми объектами — наборами элементов, которые можно итерировать: перебирать по одному и выполнять какие-то действия в зависимости от того, что нам нужно.
Для такого перебора используется цикл: он проходит по всем элементам и с каждым выполняет одни и те же инструкции. Например, можно возвести все числа в квадрат или привести все буквы в строке к верхнему или нижнему регистру.
Как выглядит алгоритм этих действий:
- Мы создаём функцию, которая будет проверять элементы в наборе.
- Передаём в
filter()
эту функцию и коллекцию элементов для проверки. - На выходе получаем отфильтрованные значения, с которыми можно продолжать работать разными способами в зависимости от логики программы.
Синтаксис и параметры функции filter()
Если в коде используется filter()
, Python ожидает два аргумента после неё: функцию с правилом фильтрации и последовательность данных для проверки.
Так выглядит синтаксис:
filter(function, iterable)
Что здесь используется:
- ключевое слово
filter()
; - первый аргумент — проверяющая функция
function
; - второй аргумент — итерируемый объект
iterable
.
Функция-аргумент должна обязательно возвращать True
или False
, подходит объект под наши условия или нет. В итоговом варианте filter()
оставит только элементы, после проверки которых мы получили True
.
Применение None вместо функции для фильтрации
Вместо первого аргумента-функции в filter()
Python разрешает использовать None
:
filter(None, iterable)
Тогда код просто проверит все элементы последовательности на то, равны они True
или False
. Логично, что для этого эти элементы должны быть логического типа. Действия после проверки такие же, как при использовании обычной функции: сохраняем True
, выбрасываем False
.
Что равно False
в Python:
False
.None
.0
(ноль).- Пустые коллекции: пустая строка
""
, пустой список[]
, пустой кортеж()
, пустой словарь{}
, пустое множество:set()
. - Объекты, которые возвращают
False
при вызове их метода__bool__
или__len__
.
Что возвращает filter()
Для примера посмотрим, как работает filter()
на примере проверки списка чисел на чётность:
# создаём функцию для проверки чётности
def is_even(x):
# возвращаем результат проверки на то,
# делится ли число на 2 без остатка
return x % 2 == 0
# исходный список
elements = [1, 2, 3, 4, 5, 6]
# создаём объект filter, применяя функцию к списку
built = filter(is_even, elements)
# выводим на экран тип объекта built
print(built)
При запуске в терминале видим, что нам вернулся не сам новый список, а какой-то объект filter object
:
<filter object at 0x10b841570>
Что это за объект и как он появился:
- Когда мы даём указание применить функцию к итерируемому объекту через
filter()
, Python делает это не сразу. Сначала он создаётfilter object
. Filter object
— это итератор ленивого типа. Это значит, что он генерирует значения только при прямом запросе, а пока запроса нет — ленится и ничего не делает.- В нашем примере ещё ничего не отфильтровалось. Итератор знает, что к списку
elements
нужно будет применить функциюis_even()
, но пока не сделал этого.
Чтобы получить отфильтрованные элементы, есть несколько вариантов.
Первый вариант — через цикл for
. Мы получим числа поэлементно, то есть они не будут объединены в коллекцию:
# получение отфильтрованных значений через цикл for
for num in built:
print(num)
Получаем:
2
4
6
Второй вариант достать отфильтрованные значения — преобразовать наш итератор в список:
# преобразование итератора в список
even_numbers = list(built)
print(even_numbers)
При запуске в консоли получаем коллекцию элементов — список:
[2, 4, 6]
Третий вариант — использовать метод итераторов next
, который возвращает элементы по одному. Когда элементы заканчиваются, выбрасывается исключение StopIteration
, после чего итератор останавливается.
# получаем объекты по одному через метод next
print("первый элемент: ", next(built))
print("второй элемент: ", next(built))
print("третий элемент: ", next(built))
Вывод на экран:
первый элемент: 2
второй элемент: 4
третий элемент: 6
В итоге элементы не хранятся в памяти и экономят ресурсы машины. Ещё итератор гибкий: его можно использовать в циклах и преобразовать в коллекцию.
Применение функции filter()
Вот несколько примеров, как в Python filter()
применяется в разных задачах.
Использование filter() с лямбда-функциями. Это простые безымянные функции, которые записаны в одну строку и которые можно присваивать переменным.
Такую функцию можно передать в качестве аргумента в фильтр. Тогда не нужно писать отдельную функцию с описанием правила фильтрации, что сократит количество кода:
# создаём список
numbers = [-3, -2, -1, 0, 1, 2, 3]
# применяем filter() с лямбда-функцией, которая
# возвращает True для положительных чисел
filtered_numbers = filter(lambda x: x > 0, numbers)
# преобразуем результат в список и выводим на экран
print(list(filtered_numbers))
Получаем результат — только положительные числа в списке:
[1, 2, 3]
Фильтрация значений NaN. NaN — сокращение от Not a Number, «не число». Это специальное значение, используемое в программировании и математике для обозначения неопределённых или невычислимых числовых результатов. Например, деление бесконечности на бесконечность или квадратный корень из отрицательного числа.
При работе с данными такие неопределённые значения могут мешать при анализе. С filter()
их можно удалить, если добавить в код модуль с математическими операциями math
.
Из math
мы возьмём функцию проверки элемента на то, равен ли он NaN
. Если равен, функция вернёт True
. Но мы перевернём это условие и сделаем так, что функция будет возвращать False
на всех NaN
-элементах. Для этого нужно написать так:
not math.isnan(value)
Так выглядит полный код для очистки коллекций от NaN
-значений:
# импортируем модуль math
import math
# создаём список, содержащий значения NaN
given = [1.0, float('nan'), 2.0, float('nan'), 3.0]
# функция, которая возвращает True, если значение не является NaN
def is_not_nan(value):
return not math.isnan(value)
# применяем filter() с функцией is_not_nan к списку given
returns = filter(is_not_nan, given)
# преобразуем результат в список и выводим
print(list(returns)) # [1.0, 2.0, 3.0]
Фильтрация кортежей. В Python есть несколько видов коллекций — наборов элементов, которые различаются принципами работы. С ними тоже можно использовать filter()
.
Для примера отфильтруем кортеж — аналог списка, в котором нельзя изменять элементы. Кортеж записывается в круглых скобках:
# создаём кортеж
items = (1, 2, 3, 4, 5, 6)
# создаём функцию для проверки, которая
# возвращает True для чисел больше 3
def specified(n):
return n > 3
# применяем filter() с функцией specified к кортежу items
filtered_tuple = filter(specified, items)
# преобразуем результат в кортеж и выводим
print(tuple(filtered_tuple))
На выходе получаем новый кортеж:
(4, 5, 6)
Фильтрация словарей. Словарь — изменяемая коллекция элементов в парах в формате «ключ: значение». Значениями могут быть любые объекты, а ключи обязательно должны быть неизменяемыми.
Все элементы в словаре хранятся в произвольном порядке, номеров у них нет. Словари записываются в фигурных скобках
Для примера мы удалим все пары, значения которых меньше 2:
# создаём словарь
based = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
# создаём функцию, которая возвращает
# True, если значение больше 2
def condition(item):
return item[1] > 2
# применяем filter() с функцией condition к элементам словаря
filtered_dict = dict(filter(condition, based.items()))
# выводим отфильтрованный словарь
print(filtered_dict)
Проверяем, что выводит программа:
{'c': 3, 'd': 4}
Комбинирование filter() с другими функциями
Filter()
может быть использована в сочетании с другими функциями для выполнения сложных операций над данными. Вот два примера.
Использование filter() с map() и reduce(). Что делают эти две функции:
Map()
берёт другую функцию и применяет её к элементам коллекции.Reduce()
тоже применяет функцию к элементам итерируемых элементов и собирает одно суммарное значение из элементов. Например, вreduce()
можно передать словарь с городами и их населением, а функция подсчитает общее население во всех городах.
Для примера мы напишем такую программу:
- возьмём список натуральных чисел от 1 до 10;
- оставим только чётные с помощью
filter()
; - удвоим каждое отфильтрованное число, используя
map()
; - суммируем все получившиеся элементы через
reduce()
.
Код будет выглядеть так:
# импортируем reduce
from functools import reduce
# создаём список натуральных чисел
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# фильтруем чётные числа
filtered_numbers = filter(lambda x: x % 2 == 0, numbers)
# удваиваем каждое отфильтрованное число
doubled_numbers = map(lambda x: x * 2, filtered_numbers)
# суммируем все удвоенные числа
sum_of_numbers = reduce(lambda x, y: x + y, doubled_numbers)
# выводим результат
print(sum_of_numbers)
Результатом будет общая сумма:
60
Фильтрация с использованием filter() и enumerate(). Enumerate()
добавляет индекс к каждому элементу последовательности. Это полезно, если нужно фильтровать данные на основе их позиции в коллекции.
Так можно профильтровать список строк, чтобы вывести только те элементы, которые стоят на чётных местах:
# создаём список из строк
words = ["using", "satisfy", "allows", "way", "like"]
# используем enumerate() для получения индексов и значений
indexed_words = enumerate(words)
# фильтруем элементы и оставляем
# только чётные индексы
filtered_words = filter(lambda pair:
pair[0] % 2 == 0, indexed_words)
# преобразуем результат в список и выводим
# только значения без индексов
print([word for _, word in filtered_words])
Запускаем код и проверяем результат:
['using', 'allows', 'like']
Сложная фильтрация с использованием filter()
Иногда нужно провести фильтрацию через несколько условий или функций. В filter()
можно объединить их через лямбда-функцию: передать аргумент, который используется во всех условиях, и перечислить функции с этими условиями. Про лямбда-функции в Python мы делали отдельную статью — там прям с нуля и с примерами.
Пример: можно создать проверку слов на то, начинаются ли они с гласных и сколько букв содержат. В итоговом перечне слов оставляем только те, длина которых больше 5:
# создаём список строк
words = ["яблоко", "банан", "апельсин", "груша",
"ежевика", "инжир", "виноград"]
# создаём функцию, которая проверяет,
# начинается ли строка с гласной буквы
def starts_with_vowel(word):
# передаём в функцию список гласных
vowels = "аеёиоуыэюя"
# возвращаем True, первая буква слова
# из списка входит в список гласных
return word[0].lower() in vowels
# создаём функцию, которая проверяет,
# что длина строки больше 5
def is_long_word(word):
return len(word) > 5
# применяем filter() с двумя условиями
# через лямбда-функцию
filtered_words = filter(lambda word:
starts_with_vowel(word) and
is_long_word(word), words)
# преобразуем результат в список и выводим
print(list(filtered_words))
Получаем слова, которые соответствуют обоим условиям сразу:
['яблоко', 'апельсин', 'ежевика']
Проблемы с изменяемыми коллекциями
Чтобы применять инструменты и технологии эффективно, нужно помнить об их ограничениях и недостатках. У filter()
они тоже есть.
Хотя сама функция не изменяет исходную коллекцию, она ссылается на неё. Если изменится коллекция, изменится и результат работы filter()
.
Например, мы применили filter()
к списку чисел. Итогом работы функции будет ленивый итератор, который считает значения только при применении. Поэтому если после применения функции коллекция изменится, а итератор ещё не вернул значения, результат работы будет зависеть уже от новой коллекции.
Ограниченная функциональность для сложной фильтрации
Если нужно провести сложную фильтрацию и включить несколько условий, код с использованием filter()
может быть трудночитаемым и громоздким.
Проще управлять логикой фильтрации через генераторы списков или циклы. К тому же они поддерживают встроенную обработку исключений, а filter()
— нет.
Когда полезна filter(), а когда — не очень
Вот краткий свод ситуаций, когда в программировании хорошо подойдёт функциональность filter()
:
- Простая фильтрация данных, когда элементы итерируемого объекта нужно отобрать по одному простому условию, которое умещается в одну строку. Например, удаление отрицательных чисел или проверка через лямбда-функцию.
- Очистка данных от мусора, такого как None, NaN и пустые строки.
- Требование экономии памяти и работа с ленивыми вычислениями, когда элементы фильтруются на лету и не занимают ресурсы компьютера.
Когда лучше применять другие инструменты:
- Сложная фильтрация с несколькими условиями. Такой код может выглядеть сложно и трудно для прочтения.
- Работа с изменяемыми коллекциями, которые планируется изменять в ходе работы программы. Результат
filter()
тоже будет изменяться, и за этим придётся следить дополнительно. - Обработка исключений. Питон не умеет обрабатывать исключения в
filter()
, в отличие от генераторов списков и циклов.
Вам слово
Приходите к нам в соцсети поделиться своим мнением о статье и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте: шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!