Функция filter() в Python

Функция filter() в Python

Простой способ добавить нужное в корзину

Сегодня рассказываем про ещё одну простую, но интересную возможность на 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(), в отличие от генераторов списков и циклов.

Вам слово

Приходите к нам в соцсети поделиться своим мнением о статье и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте: шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!

Обложка:

Алексей Сухов

Корректор:

Елена Грицун

Вёрстка:

Мария Климентьева

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Вам может быть интересно
easy
[anycomment]
Exit mobile version