Python not in мануал

Время чтения 2 мин.

Чтобы проверить, отсутствует ли какой-либо элемент в последовательности, списке, строке, кортеже или наборе, используйте оператор not in. Оператор not in является полной противоположностью оператора in.

Оператор in в Python проверяет, является ли указанное значение составным элементом последовательности.

Содержание

  1. Что такое оператор Not In в Python?
  2. Код Python для оператора not in
  3. Работа с операторами «in» и «not in» в словарях

Not in — это встроенный оператор Python, который проверяет наличие заданного значения внутри заданной последовательности, но его значения противоположны значениям оператора in. Оператор not-in возвращает логическое значение в качестве вывода. Он возвращает либо True, либо False.

Рассмотрим пример кода с оператором Not in в Python.

Код Python для оператора not in

listA = [11, 21, 29, 46, 19]

stringA = «Hello! This is AppDividend»

tupleA =(11, 22, 33, 44)

print(19 not in listA)

print(«is» not in stringA)

print(55 not in tupleA)

Вывод:

Когда вы используете оператор not in в условии, оператор возвращает логический результат, оценивающий True или False. В нашем примере сначала мы проверяем элемент из списка. Он возвращает False, потому что 19 находится в listA.

stringA содержит «is» в качестве подстроки, и поэтому возвращается False. Когда указанное значение находится внутри последовательности, оператор возвращает True. В то время как, когда он не найден, мы получаем False.

Работа с операторами «in» и «not in» в словарях

Словари не являются последовательностями, потому что они индексируются на основе ключей. Посмотрим, как работать с оператором not in в словарях.

Попробуем разобраться на примере.

dict1 = {11: «eleven», 21: «twenty one», 46: «fourty six», 10: «ten»}

print(«eleven» in dict1)

print(«eleven» not in dict1)

print(21 in dict1)

print(21 not in dict1)

print(10 in dict1)

print(10 not in dict1)

Вывод:

False

True

True

False

True

False

Операторы in и not in в Python позволяют быстро определить, является ли данное значение частью коллекции значений. Такой тип проверки часто встречается в программировании, и в Python он известен как тест на принадлежность. Поэтому эти операторы известны как операторы принадлежности.

Содержание

  • Проверка принадлежности в Python
  • Оператор in в Python
  • Оператор not in в Python
  • Использование in и not in с различными типами Python
    • Использование in со списками, кортежами и диапазонами в Python
    • Использование операторов членства со строками
    • Использование операторов in и not in с генераторами
    • Использование in и not in со словарями и множествами в Python
  • Применение операторов in и not in в Python
    • Замена цепочек операторов or
    • Написание эффективных тестов на членство
  • Использование operator.contains() для тестов членства
  • Поддержка тестов членства в пользовательских классах

Иногда требуется выяснить, присутствует ли значение в коллекции. Другими словами, вам нужно проверить, является ли данное значение членом коллекции значений. Такой тип проверки обычно называют тестом на принадлежность.

Вероятно, естественным способом выполнения такой проверки является итерационный перебор значений и сравнение их с целевым значением. Это можно сделать с помощью цикла for и условного оператора.

Рассмотрим следующую функцию is_member():

>>> def is_member(value, iterable):
...     for item in iterable:
...         if value is item or value == item:
...             return True
...     return False
...

Эта функция принимает два аргумента: целевое значение и коллекцию значений (итерируемый объект). Цикл перебирает итерируемый объект, а условный оператор проверяет, равно ли целевое значение текущему. Обратите внимание, что условие проверяет идентичность объектов с помощью оператора is или равенство значений с помощью оператора равенства ==. Это немного разные, но взаимодополняющие проверки.

Если условие истинно, то функция возвращает True, выходя из цикла. Этот ранний возврат замыкает работу цикла. Если цикл завершается без какого-либо совпадения, то функция возвращает False:

>>> is_member(5, [2, 3, 5, 9, 7])
True

>>> is_member(8, [2, 3, 5, 9, 7])
False

Первый вызов функции is_member() возвращает True, потому что целевое значение 5 является членом рассматриваемого списка [2, 3, 5, 9, 7]. Второй вызов функции возвращает False, потому что 8 не присутствует во входном списке значений.

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

Оператор Описание Синтаксис
in Возвращает True, если целевое значение присутствует в коллекции значений. В противном случае возвращает False value in collection
not in Возвращает True, если целевое значение НЕ присутствует в коллекции значений. В противном случае возвращает False value not in collection

Как и в случае с булевыми операторами, Python предпочитает удобство чтения, используя в качестве операторов обычные английские слова, а не потенциально непонятные символы.


Примечание: Не путайте ключевое слово in, когда оно работает как оператор членства, с ключевым словом in в синтаксисе цикла for. Они имеют совершенно разные значения. Оператор in в Python проверяет, входит ли значение в коллекцию значений, а ключевое слово in в цикле for указывает на итерируемый объект.


Как и многие другие операторы, in и not in являются бинарными. Это означает, что вы можете создавать выражения, соединяя два операнда. В данном случае это:

  • Левый операнд – целевое значение, которое вы хотите найти в коллекции значений
  • Правый операнд – коллекция значений, в которой может быть найдено целевое значение.

Синтаксис теста на принадлежность выглядит следующим образом:

value in collection
или
value not in collection

В этих выражениях value может быть любым объектом Python. А collection может быть любым типом данных, который может содержать коллекции значений, включая списки, кортежи, строки, множества и словари. Это также может быть класс, реализующий метод .__contains__(), или пользовательский класс, явно поддерживающий тесты принадлежности или итерацию.

Если вы правильно используете операторы in и not in, то выражения, которые вы построите с их помощью, всегда будут иметь булево значение. Другими словами, эти выражения всегда будут возвращать либо True, либо False.

С другой стороны, если вы попытаетесь найти значение в каком-то объекте, который не поддерживает тесты принадлежности, то получите ошибку TypeError. Об ошибках и их обработке можно почитать в статье “Как обрабатывать исключения в Python”.

Поскольку операторы членства всегда оцениваются как булево значение, Python считает их булевыми операторами, как и операторы and, or и not.

Теперь, когда вы знаете, что такое операторы членства, пришло время изучить основы их работы.

Примечание редакции. О других операторах можно почитать в статье “Операторы в Python”.

Оператор in в Python

Чтобы лучше понять оператор in в Python, начните с написания небольших наглядных примеров. Попробуйте определить, есть ли указанное значение в списке:

>>> 5 in [2, 3, 5, 9, 7]
True

>>> 8 in [2, 3, 5, 9, 7]
False

Первое выражение возвращает True, потому что 5 есть в списке чисел. Второе выражение возвращает False, потому что 8 не присутствует в списке.

Согласно документации по оператору in, выражение типа value in collection эквивалентно следующему коду:

any(value is item or value == item for item in collection)

Генераторное выражение, обернутое в вызов any(), строит список булевых значений. Эти значения получаются в результате проверки идентичности или равенства целевого значения текущему элементу коллекции.

Вызов any() проверяет, является ли хоть одно из полученных булевых значений True. В этом случае функция возвращает True. Если все значения False, то any() возвращает False.

Оператор not in в Python

Оператор not in при проверке принадлежности делает прямо противоположное. С помощью этого оператора вы можете проверить, не входит ли заданное значение в набор значений:

>>> 5 not in [2, 3, 5, 9, 7]
False

>>> 8 not in [2, 3, 5, 9, 7]
True

В первом примере вы получите False, потому что в списке [2, 3, 5, 9, 7] есть число 5. Во втором примере вы получите True, потому что 8 нет в списке значений. Чтобы избежать путаницы, помните: вы пытаетесь проверить, что значение НЕ является частью заданного набора значений.

После этого краткого обзора работы операторов принадлежности вы готовы перейти на следующий уровень и узнать, как in и not in работают с различными встроенными типами данных.

Примечание: Конструкция not value in collection работает так же, как и конструкция value not in collection. Однако первый вариант сложнее для чтения. Поэтому используйте not in как единый оператор, а не not для отрицания результата in.

Использование in и not in с различными типами Python

Все встроенные последовательности, такие как списки, кортежи, объекты диапазона и строки, поддерживают тесты принадлежности с помощью операторов in и not in.

Коллекции, такие как множества и словари, также поддерживают эти тесты.

По умолчанию операции членства над словарями проверяют, есть ли в словаре заданный ключ. Однако словари также имеют методы, которые позволяют использовать операторы членства с ключами, значениями и парами ключ-значение.

В следующих разделах вы узнаете о некоторых особенностях использования in и not in с различными встроенными типами данных. Начнем со списков, кортежей и объектов диапазона.

Использование in со списками, кортежами и диапазонами в Python

Мы уже рассматривали примеры использования операторов in и not in для определения наличия заданного значения в существующем списке значений. То есть вы уже знакомы с тем, как тесты членства работают со списками.

С кортежами операторы членства работают так же, как и со списками:

>>> 5 in (2, 3, 5, 9, 7)
True

>>> 5 not in (2, 3, 5, 9, 7)
False

Здесь нет никаких сюрпризов. В первом примере оператор in возвращает True, потому что целевое значение (5) есть в кортеже. Во втором примере оператор not in возвращает противоположный результат.

Для списков и кортежей операторы членства используют алгоритм поиска, который перебирает элементы базовой коллекции. Поэтому, по мере увеличения длины итерируемого объекта, время поиска увеличивается прямо пропорционально. Используя нотацию большого “О”, можно сказать, что операции членства над этими типами данных имеют временную сложность O(n).

Если вы используете операторы in и not in с объектами диапазона (range object – результат работы функции range()), то получите аналогичный результат:

>>> 5 in range(10)
True

>>> 5 not in range(10)
False

>>> 5 in range(0, 10, 2)
False

>>> 5 not in range(0, 10, 2)
True

Когда речь идет об объектах диапазона, использование тестов членства может показаться на первый взгляд излишним. В большинстве случаев вы заранее знаете значения в результирующем диапазоне. Но что, если вы используете range() со смещениями, которые определяются во время выполнения?


Примечание: При создании объектов диапазона в range() можно передать до трех аргументов. Аргумент start определяет число, с которого начинается диапазон. Аргумент stop – число, на котором диапазон должен прекратить генерировать значения. А step – это шаг между генерируемыми значениями. Эти три аргумента называют смещениями.


Рассмотрим следующие примеры, в которых шаг определяется случайным образом во время выполнения:

>>> from random import randint

>>> 50 in range(0, 100, randint(1, 10))
False

>>> 50 in range(0, 100, randint(1, 10))
False

>>> 50 in range(0, 100, randint(1, 10))
True

>>> 50 in range(0, 100, randint(1, 10))
True

На вашей машине вы можете получить другие результаты, поскольку вы работаете со случайными смещениями диапазона. В данном примере step – единственное изменяемое смещение. В реальном коде смещения start и stop тоже могут быть вариативными.

Для объектов диапазона алгоритм, лежащий в основе тестов на принадлежность, вычисляет наличие заданного значения в range object. Делается это с помощью выражения (value - start) % step) == 0, которое учитывает смещения, использованные для создания данного диапазона. Благодаря этому тесты на принадлежность очень эффективны при работе с объектами диапазона. В этом случае можно сказать, что их временная сложность равна O(1).


Примечание: Списки, кортежи и объекты диапазона имеют метод .index(), который возвращает индекс первого вхождения заданного значения в базовую последовательность. Этот метод полезен для определения местоположения значения в последовательности.

Некоторые могут подумать, что с помощью этого метода можно определить, находится ли значение в последовательности. Однако если значение не находится в последовательности, то .index() выдает ошибку ValueError:

>>> (2, 3, 5, 9, 7).index(8)
Traceback (most recent call last):
    ...
ValueError: tuple.index(x): x not in tuple

Вероятно, вы не захотите выяснять, находится ли значение в последовательности, вызывая исключения, поэтому для этой цели следует использовать оператор членства вместо .index().


Помните, что целевое значение в тесте на принадлежность может быть любого типа. Тест будет проверять, есть ли это значение в целевой коллекции. Допустим, у вас есть приложение, в котором пользователи проходят аутентификацию с помощью имени пользователя и пароля:

# users.py

username = input("Username: ")
password = input("Password: ")

users = [("john", "secret"), ("jane", "secret"), ("linda", "secret")]

if (username, password) in users:
    print(f"Hi {username}, you're logged in!")
else:
    print("Wrong username or password")

Это наивный пример. Вряд ли кто-то будет так обращаться со своими пользователями и паролями. Но пример показывает, что целевое значение может быть любого типа данных. В данном случае вы используете кортеж строк, представляющих имя пользователя и его пароль.

Вот как этот код работает на практике:

$ python users.py
Username: john
Password: secret
Hi john, you're logged in!

$ python users.py
Username: tina
Password: secret
Wrong username or password

В первом примере имя пользователя и пароль верны, потому что они есть в списке users. Во втором примере имя пользователя не принадлежит ни одному зарегистрированному пользователю, поэтому аутентификация не проходит.

В этих примерах важно отметить, что порядок хранения данных в кортеже имеет решающее значение, потому что при сравнении кортежей что-то вроде ("john", "secret") не равно ("secret", "john"), несмотря на то, что элементы одинаковые.

В этом разделе мы рассмотрели примеры, демонстрирующие основное поведение операторов членства с обычными встроенными последовательностями Python. Однако осталась еще одна встроенная последовательность. Да, строки!

Использование операторов членства со строками

Строки – это фундаментальный инструмент в наборе инструментов каждого разработчика Python. Как и кортежи, списки и диапазоны, строки также являются последовательностями, поскольку их элементы или символы последовательно хранятся в памяти.

Вы можете использовать операторы in и not in со строками, когда вам нужно выяснить, присутствует ли данный символ в целевой строке. Предположим, что вы используете строки для установки и управления правами доступа к ресурсу:

>>> class User:
...     def __init__(self, username, permissions):
...         self.username = username
...         self.permissions = permissions
...

>>> admin = User("admin", "wrx")
>>> john = User("john", "rx")

>>> def has_permission(user, permission):
...     return permission in user.permissions
...

>>> has_permission(admin, "w")
True
>>> has_permission(john, "w")
False

Класс User принимает два аргумента: имя пользователя и набор разрешений. Для указания разрешений используется строка, в которой w означает, что пользователь имеет право на запись, r – право на чтение, а x – право на выполнение. Обратите внимание, что это те же буквы, что и в разрешениях файловой системы в стиле Unix.

Тест членства в has_permission() проверяет, имеет ли текущий пользователь заданное разрешение, возвращая True или False соответственно. Для этого оператор in ищет соответствующий символ в строке permissions. В данном примере вы хотите узнать, есть ли у пользователя разрешение на запись.

Однако в вашей системе разрешений есть скрытая проблема. Что произойдет, если вы вызовете функцию с пустой строкой? Вот вам ответ:

>>> has_permission(john, "")
True

Поскольку пустая строка всегда считается подстрокой любой другой строки, выражение типа "" в user.permissions вернет True. Это может привести к нарушению безопасности в вашей системе.

Вы также можете использовать операторы членства, чтобы определить, содержит ли строка подстроку:

>>> greeting = "Hi, welcome to Real Python!"

>>> "Hi" in greeting
True
>>> "Hi" not in greeting
False

>>> "Hello" in greeting
False
>>> "Hello" not in greeting
True

Для строкового типа данных выражение типа substring in string будет истинным, если substring является частью string. В противном случае выражение равно False.


Примечание: В отличие от других последовательностей, таких как списки, кортежи и объекты диапазона, строки предоставляют метод .find(), который можно использовать при поиске заданной подстроки в существующей строке. Например:

>>> greeting.find("Python")
20

>>> greeting.find("Hello")
-1

Если подстрока присутствует в исходной строке, то .find() возвращает индекс, с которого она начинается. Если целевая строка не содержит подстроки, то в результате вы получите -1. Таким образом, выражение string.find(substring) >= 0 будет эквивалентно проверке substring in string.

Однако тест на принадлежность гораздо более читабельный и явный, что делает его предпочтительным в данной ситуации.


Важный момент, который следует учитывать при использовании тестов членства в строках: сравнения строк чувствительны к регистру:

>>> "PYTHON" in greeting
False

Этот тест на принадлежность возвращает False, поскольку сравнение строк чувствительно к регистру, а “PYTHON” в верхнем регистре не присутствует в приветствии. Чтобы это обойти, можно нормализовать все ваши строки, используя метод .upper() или .lower():

>>> "PYTHON".lower() in greeting.lower()
True

В этом примере вы используете .lower() для перевода целевой подстроки и исходной строки в нижний регистр. Это преобразование обманывает чувствительность к регистру в неявном сравнении строк.

Использование операторов in и not in с генераторами

Функции-генераторы и генераторные выражения создают эффективные с точки зрения памяти объекты-итераторы. Для экономии памяти эти итераторы выдают элементы по требованию, не сохраняя в памяти полный ряд значений.

На практике генераторная функция – это функция, которая использует оператор yield в своем теле. Допустим, вам нужна функция-генератор, которая принимает список чисел и возвращает итератор, выдающий значения из исходных данных, возведенные в квадрат. В этом случае можно поступить следующим образом:

>>> def squares_of(values):
...     for value in values:
...         yield value ** 2
...

>>> squares = squares_of([1, 2, 3, 4])

>>> next(squares)
1
>>> next(squares)
4
>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

Эта функция возвращает объект-итератор, который выдает квадраты чисел по запросу. Для получения последовательных значений из итератора можно использовать встроенную функцию next(). Когда объект-итератор полностью исчерпан, он вызывает исключение StopIteration, чтобы сообщить, что больше значений не осталось.

В функции-генераторе типа squares_of() можно использовать операторы членства:

>>> 4 in squares_of([1, 2, 3, 4])
True
>>> 9 in squares_of([1, 2, 3, 4])
True
>>> 5 in squares_of([1, 2, 3, 4])
False

Оператор in работает, как и ожидалось. При использовании с итератором он возвращает True, если значение присутствует в итераторе, и False в противном случае.

Однако при проверке принадлежности необходимо учитывать некоторые моменты. Итератор выдает каждый элемент только один раз. Если вы используете все элементы, то итератор будет исчерпан, и вы не сможете повторить итерацию. Если вы извлечете из итератора некоторые элементы, то дальнейшие итерации возможны только над оставшимися элементами.

Когда вы используете in или not in для итератора, при поиске целевого значения они будут исчерпывать итератор. Если значение присутствует в итераторе, то оператор употребит все значения итератора до целевого. Остальные значения по-прежнему будут доступны в итераторе:

>>> squares = squares_of([1, 2, 3, 4])

>>> 4 in squares
True

>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

В этом примере в итераторе есть значение 4, потому что это квадрат числа 2. Поэтому in возвращает True. Когда вы используете next() для получения следующего значения итератора, вы получаете 9 (квадрат числа 3). Этот результат подтверждает, что у вас больше нет доступа к первым двум значениям. Вы можете продолжать вызывать next() до тех пор, пока не получите исключение StopIteration, когда итератор будет исчерпан.

Аналогично, если значение не присутствует в итераторе, то оператор полностью исчерпает итератор, и вы не будете иметь доступа ни к одному из его значений:

>>> squares = squares_of([1, 2, 3, 4])

>>> 5 in squares
False

>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

В этом примере оператор in полностью использует все значения и возвращает False, потому что целевое значение отсутствует. Поскольку итератор теперь исчерпан, вызов next() приводит к ошибке StopIteration.

Итераторы также можно создавать с помощью генераторных выражений. Эти выражения используют тот же синтаксис, что и list comprehension,но квадратные скобки [] заменяются круглыми (). С результатом работы генераторного выражения можно использовать операторы in и not in:

>>> squares = (value ** 2 for value in [1, 2, 3, 4])
>>> squares
<generator object <genexpr> at 0x1056f20a0>

>>> 4 in squares
True

>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

Переменная squares теперь содержит итератор, полученный в результате работы генераторного выражения. Этот итератор выдает квадраты входного списка чисел. Итераторы, полученные из генераторных выражений работают так же, как и полученные из функций-генераторов. Поэтому при их использовании в тестах членства действуют те же правила.

При использовании операторов in и not in с итераторами может возникнуть еще одна критическая проблема. В частности, она может возникнуть при работе с бесконечными итераторами. Приведенная ниже функция возвращает итератор, который дает бесконечное количество целых чисел:

>>> def infinite_integers():
...     number = 0
...     while True:
...         yield number
...         number += 1
...

>>> integers = infinite_integers()
>>> integers
<generator object infinite_integers at 0x1057e8c80>

>>> next(integers)
0
>>> next(integers)
1
>>> next(integers)
2
>>> next(integers)
3
>>> next(integers)

Функция infinite_integers() возвращает итератор, выдающий по запросу целые числа. Но помните, что значений будет бесконечное множество. Поэтому не стоит использовать операторы членства с этим итератором. Почему? Ну, если целевого значения не окажется в итераторе, то вы столкнетесь с бесконечным циклом, из-за которого ваша программа зависнет.

Использование in и not in со словарями и множествами в Python

Операторы членства Python также работают со словарями и множествами. Если вы примените операторы in или not in непосредственно к словарю, то они будут проверять наличие заданного ключа. Такую проверку можно выполнить с применением метода .keys(), который более явно выразит ваши намерения.

Вы также можете проверить, находится ли заданное значение или пара ключ-значение в словаре. Для этих проверок можно использовать методы .values() и .items() соответственно:

>>> likes = {"color": "blue", "fruit": "apple", "pet": "dog"}

>>> "fruit" in likes
True
>>> "hobby" in likes
False
>>> "blue" in likes
False

>>> "fruit" in likes.keys()
True
>>> "hobby" in likes.keys()
False
>>> "blue" in likes.keys()
False

>>> "dog" in likes.values()
True
>>> "drawing" in likes.values()
False

>>> ("color", "blue") in likes.items()
True
>>> ("hobby", "drawing") in likes.items()
False

В этих примерах вы используете оператор in непосредственно для словаря likes, чтобы проверить, есть ли в словаре ключи “fruit”, “hobby” и “blue”. Обратите внимание: несмотря на наличие значения “blue” тест возвращает False, потому что он учитывает только ключи.

Далее вы используете метод .keys(), чтобы получить те же результаты. В данном случае явное название метода делает ваши намерения гораздо более понятными для других программистов, читающих ваш код.

Чтобы проверить, присутствует ли в likes значение типа “dog” или “drawing”, используется метод .values(), который возвращает объект представления со значениями в базовом словаре. Аналогично, чтобы проверить, содержится ли пара ключ-значение в likes, используется метод .items(). Обратите внимание, что целевые пары ключ-значение должны быть кортежами из двух элементов с ключом и значением в указанном порядке.

Что касается множеств, с ними операторы членства работают так же, как и со списками или кортежами:

>>> fruits = {"apple", "banana", "cherry", "orange"}

>>> "banana" in fruits
True
>>> "banana" not in fruits
False

>>> "grape" in fruits
False
>>> "grape" not in fruits
True

Эти примеры показывают, что вы можете проверить, содержится ли данное значение во множестве, используя операторы членства in и not in.

Теперь, когда вы знаете, как операторы in и not in работают с различными встроенными типами данных, давайте попробуем их применить.

Применение операторов in и not in в Python

Тесты на членство с in и not in – довольно распространенные операции в программировании. Вы найдете такие проверки во многих кодовых базах Python, и в своем коде вы тоже будете их использовать.

В следующих разделах мы рассмотрим, как заменить булевы выражения с несколькими операторами or на тесты принадлежности. Поскольку тесты принадлежности могут довольно часто встречаться в вашем коде, мы также разберем, как сделать их более эффективными.

Замена цепочек операторов or

Использование теста принадлежности для замены сложного булева выражения c несколькими операторами or – полезный прием, позволяющий упростить код и сделать его более читабельным.

Предположим, что вам нужно написать функцию, которая принимает название цвета в виде строки и определяет, является ли этот цвет основным. Для этого вы будете использовать цветовую модель RGB (красный, зеленый и синий в этой модели – основные цвета):

>>> def is_primary_color(color):
...     color = color.lower()
...     return color == "red" or color == "green" or color == "blue"
...

>>> is_primary_color("yellow")
False

>>> is_primary_color("green")
True

В функции is_primary_color() используется составное булево выражение. С помощью оператора or это выражение проверяет, является ли входной цвет красным, зеленым или синим. Несмотря на то, что эта функция работает, условие может быть запутанным и трудным для чтения и понимания.

Приведенное выше условие можно заменить компактным и читабельным тестом принадлежности:

>>> def is_primary_color(color):
...     primary_colors = {"red", "green", "blue"}
...     return color.lower() in primary_colors
...

>>> is_primary_color("yellow")
False

>>> is_primary_color("green")
True

Теперь ваша функция для проверки, является ли входной цвет красным, зеленым или синим, использует оператор in. Присвоение множества основных цветов правильно названной переменной типа primary_colors также помогает сделать ваш код более читабельным. Последняя проверка теперь довольно ясна. Любой, кто прочитает ваш код, сразу поймет, что вы пытаетесь определить, является ли входной цвет основным в соответствии с цветовой моделью RGB.

Как видите, основные цвета мы сохранили во множестве. Почему? Ответ вы найдете в следующем разделе.

Написание эффективных тестов на членство

Для реализации словарей и множеств в Python используется структура данных, называемая хэш-таблицей. Хеш-таблицы обладают замечательным свойством: поиск любого заданного значения в структуре данных занимает примерно одинаковое время, независимо от того, сколько значений в таблице. Используя нотацию Big O, можно сказать, что поиск значений в хэш-таблицах имеет временную сложность O(1). То есть они супербыстрые.

Итак, какое отношение эта особенность хэш-таблиц имеет к тестам на принадлежность со словарями и множествами? Оказывается, с этими типами операторы in и not in работают очень быстро. Если в тестах на принадлежность отдавать предпочтение словарям и множествам, а не спискам и другим типам данных, это позволит оптимизировать производительность вашего кода.

Чтобы получить представление о том, насколько эффективнее списка может быть множество, создайте следующий сценарий:

# performance.py

from timeit import timeit

a_list = list(range(100_000))
a_set = set(range(100_000))

list_time = timeit("-1 in a_list", number=1, globals=globals())
set_time = timeit("-1 in a_set", number=1, globals=globals())

print(f"Sets are {(list_time / set_time):.2f} times faster than Lists")

Этот скрипт создает список целых чисел со ста тысячами значений и множество с таким же количеством элементов. Затем скрипт вычисляет время, необходимое для определения наличия числа -1 в списке и множестве. Предварительно известно, что -1 не встречается ни в списке, ни во множестве. Поэтому оператору членства придется проверить все значения, прежде чем получить окончательный результат.

Как вы уже знаете, когда оператор in ищет значение в списке, он использует алгоритм с временной сложностью O(n). А при поиске значения во множестве он использует алгоритм поиска в хэш-таблице, временная сложность которого равна O(1). Этот факт может иметь большое значение с точки зрения производительности.

Запустите свой скрипт из командной строки с помощью следующей команды:

$ python performance.py
Sets are 1563.33 times faster than Lists

Хотя вывод вашей команды может немного отличаться, он все равно покажет значительную разницу в производительности, если вы используете множество вместо списка. При использовании списка время обработки будет пропорционально количеству значений. При использовании множества время будет практически одинаковым для любого количества значений.

Этот тест производительности показывает, что когда ваш код выполняет проверку принадлежности к большим коллекциям значений, следует использовать множества вместо списков. Вы также выиграете от использования множеств, если ваш код выполняет несколько проверок на принадлежность к группе во время выполнения.

Однако обратите внимание, что не стоит преобразовывать существующий список во множество только для того, чтобы выполнить несколько тестов на принадлежность. Помните, что преобразование списка в множество – это операция с временной сложностью O(n).

Использование operator.contains() для тестов членства

Оператор in имеет эквивалентную функцию в модуле operator, который входит в стандартную библиотеку Python. Эта функция называется contains(). Она принимает два аргумента – коллекцию значений и целевое значение. Функция возвращает True, если входная коллекция содержит целевое значение:

>>> from operator import contains

>>> contains([2, 3, 5, 9, 7], 5)
True

>>> contains([2, 3, 5, 9, 7], 8)
False

Первым аргументом функции contains() является коллекция значений, а вторым – целевое значение. Обратите внимание, что порядок аргументов отличается от обычной операции членства, где целевое значение стоит на первом месте.

Эта функция может пригодиться, когда вы используете такие инструменты, как map() или filter() для обработки итерируемых объектов. Например, у вас есть набор декартовых координат, хранящихся в виде кортежей в списке. Вы хотите создать новый список, содержащий только те точки, которые не находятся над осью координат. Используя функцию filter(), вы можете прийти к следующему решению:

>>> points = [
...     (1, 3),
...     (5, 0),
...     (3, 7),
...     (0, 6),
...     (8, 3),
...     (2, 0),
... ]

>>> list(filter(lambda point: not contains(point, 0), points))
[(1, 3), (3, 7), (8, 3)]

В этом примере вы при помощи функции filter() получаете точки, которые не содержат координату 0. Для этого вы используете contains() в лямбда-функции. Так как filter() возвращает итератор, вы оборачиваете все в вызов list(), чтобы преобразовать итератор в список точек.

Хотя эта конструкция работает, она довольно сложна, поскольку подразумевает импорт contains(), создание лямбда-функции поверх нее и вызов нескольких функций. Вы можете получить тот же результат, используя представление списка либо с contains(), либо с оператором not in:

>>> [point for point in points if not contains(point, 0)]
[(1, 3), (3, 7), (8, 3)]

>>> [point for point in points if 0 not in point]
[(1, 3), (3, 7), (8, 3)]

Такие представления списка короче и, вероятно, более читабельны, чем эквивалентный вызов filter() из предыдущего примера. Они также менее сложны, поскольку вам не нужно создавать лямбда-функцию или вызывать list(), что снижает требования к знаниям.

Поддержка тестов членства в пользовательских классах

Предоставление метода .__contains__() является наиболее явным и предпочтительным способом поддержки тестов членства в ваших собственных классах. Python автоматически вызывает этот специальный метод, когда вы используете экземпляр вашего класса в качестве правого операнда в тесте на принадлежность.

Скорее всего, вы добавите метод .__contains__() только в классы, которые будут работать как коллекции значений. Таким образом пользователи вашего класса смогут определить, хранится ли заданное значение в конкретном экземпляре вашего класса.

Рассмотрим пример. Допустим, вам нужно создать минимальную стековую структуру данных для хранения значений по принципу LIFO (last in, first out). Одним из требований к вашей пользовательской структуре данных является поддержка тестов членства. В итоге вы пишете следующий класс:

# stack.py

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __contains__(self, item):
        return item in self.items

Ваш класс Stack поддерживает две основные функции стека. Вы можете поместить значение на вершину стека и извлечь значение с вершины стека. Обратите внимание, что под капотом ваша структура данных использует объект list для хранения и манипулирования фактическими данными.

Ваш класс также поддерживает тесты принадлежности с помощью операторов in и not in. Для этого в классе реализован метод .__contains__(), который опирается на сам оператор in.

Чтобы опробовать свой класс, запустите следующий код:

>>> from stack import Stack

>>> stack = Stack()
>>> stack.push(1)
>>> stack.push(2)
>>> stack.push(3)

>>> 2 in stack
True
>>> 42 in stack
False
>>> 42 not in stack
True

Ваш класс полностью поддерживает операторы in и not in. Отличная работа! Теперь вы знаете, как поддерживать тесты членства в своих классах.

Обратите внимание, что если у данного класса есть метод .__contains__(), то класс не обязательно должен быть итерируемым, чтобы операторы членства работали. В приведенном выше примере Stack не является итерируемым, но операторы все равно работают, потому что они получают результат из метода .__contains__().

Кроме метода .__contains__() существует еще как минимум два способа поддержки тестов на членство в пользовательских классах. Если в вашем классе есть метод .__iter__() или .__getitem__(), то операторы in и not in также работают.

Рассмотрим следующую альтернативную версию Stack:

# stack.py

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __iter__(self):
        yield from self.items

Специальный метод .__iter__() делает ваш класс итерабельным, а этого достаточно для работы тестов на членство. Попробуйте!

Другой способ поддержки тестов на принадлежность – реализовать в ваших классах метод .__getitem__(), который обрабатывает операции индексирования с использованием целочисленных индексов:

# stack.py

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __getitem__(self, index):
        return self.items[index]

Python автоматически вызывает метод .__getitem__(), когда вы выполняете операции индексирования базового объекта. В этом примере, когда вы делаете stack[0], вы получаете первый элемент в экземпляре Stack. Python использует преимущества метода .__getitem__(), чтобы операторы членства работали правильно.

Заключение

Теперь вы знаете, как выполнять тесты на принадлежность, используя операторы in и not in в Python. Такие проверки позволяют выяснить, присутствует ли заданное значение в коллекции значений. Это довольно распространенная операция в программировании.

Перевод статьи Leodanis Pozo Ramos «Python’s “in” and “not in” Operators: Check for Membership».

Python’s in and not in operators allow you to quickly determine if a given value is or isn’t part of a collection of values. This type of check is common in programming, and it’s generally known as a membership test in Python. Therefore, these operators are known as membership operators.

In this tutorial, you’ll learn how to:

  • Perform membership tests using the in and not in operators
  • Use in and not in with different data types
  • Work with operator.contains(), the equivalent function to the in operator
  • Provide support for in and not in in your own classes

To get the most out of this tutorial, you’ll need basic knowledge of Python, including built-in data types, such as lists, tuples, ranges, strings, sets, and dictionaries. You’ll also need to know about Python generators, comprehensions, and classes.

Getting Started With Membership Tests in Python

Sometimes you need to find out whether a value is present in a collection of values or not. In other words, you need to check if a given value is or is not a member of a collection of values. This kind of check is commonly known as a membership test.

Arguably, the natural way to perform this kind of check is to iterate over the values and compare them with the target value. You can do this with the help of a for loop and a conditional statement.

Consider the following is_member() function:

>>>

>>> def is_member(value, iterable):
...     for item in iterable:
...         if value is item or value == item:
...             return True
...     return False
...

This function takes two arguments, the target value and a collection of values, which is generically called iterable. The loop iterates over iterable while the conditional statement checks if the target value is equal to the current value. Note that the condition checks for object identity with is or for value equality with the equality operator (==). These are slightly different but complementary tests.

If the condition is true, then the function returns True, breaking out of the loop. This early return short-circuits the loop operation. If the loop finishes without any match, then the function returns False:

>>>

>>> is_member(5, [2, 3, 5, 9, 7])
True

>>> is_member(8, [2, 3, 5, 9, 7])
False

The first call to is_member() returns True because the target value, 5, is a member of the list at hand, [2, 3, 5, 9, 7]. The second call to the function returns False because 8 isn’t present in the input list of values.

Membership tests like the ones above are so common and useful in programming that Python has dedicated operators to perform these types of checks. You can get to know the membership operators in the following table:

Operator Description Syntax
in Returns True if the target value is present in a collection of values. Otherwise, it returns False. value in collection
not in Returns True if the target value is not present in a given collection of values. Otherwise, it returns False. value not in collection

As with Boolean operators, Python favors readability by using common English words instead of potentially confusing symbols as operators.

Like many other operators, in and not in are binary operators. That means you can create expressions by connecting two operands. In this case, those are:

  1. Left operand: The target value that you want to look for in a collection of values
  2. Right operand: The collection of values where the target value may be found

The syntax of a membership test looks something like this:

value in collection

value not in collection

In these expressions, value can be any Python object. Meanwhile, collection can be any data type that can hold collections of values, including lists, tuples, strings, sets, and dictionaries. It can also be a class that implements the .__contains__() method or a user-defined class that explicitly supports membership tests or iteration.

If you use the in and not in operators correctly, then the expressions that you build with them will always evaluate to a Boolean value. In other words, those expressions will always return either True or False. On the other hand, if you try and find a value in something that doesn’t support membership tests, then you’ll get a TypeError. Later, you’ll learn more about the Python data types that support membership tests.

Because membership operators always evaluate to a Boolean value, Python considers them Boolean operators just like the and, or, and not operators.

Now that you know what membership operators are, it’s time to learn the basics of how they work.

Python’s in Operator

To better understand the in operator, you’ll start by writing some small demonstrative examples that determine if a given value is in a list:

>>>

>>> 5 in [2, 3, 5, 9, 7]
True

>>> 8 in [2, 3, 5, 9, 7]
False

The first expression returns True because 5 appears inside your list of numbers. The second expression returns False because 8 isn’t present in the list.

According to the in operator documentation, an expression like value in collection is equivalent to the following code:

any(value is item or value == item for item in collection)

The generator expression wrapped in the call to any() builds a list of the Boolean values that result from checking if the target value has the same identity or is equal to the current item in collection. The call to any() checks if any one of the resulting Boolean values is True, in which case the function returns True. If all the values are False, then any() returns False.

Python’s not in Operator

The not in membership operator does exactly the opposite. With this operator, you can check if a given value is not in a collection of values:

>>>

>>> 5 not in [2, 3, 5, 9, 7]
False

>>> 8 not in [2, 3, 5, 9, 7]
True

In the first example, you get False because 5 is in [2, 3, 5, 9, 7]. In the second example, you get True because 8 isn’t in the list of values. This negative logic may seem like a tongue twister. To avoid confusion, remember that you’re trying to determine if the value is not part of a given collection of values.

With this quick overview of how membership operators work, you’re ready to go to the next level and learn how in and not in work with different built-in data types.

Using in and not in With Different Python Types

All built-in sequences—such as lists, tuples, range objects, and strings—support membership tests with the in and not in operators. Collections like sets and dictionaries also support these tests. By default, membership operations on dictionaries check whether the dictionary has a given key or not. However, dictionaries also have explicit methods that allow you to use the membership operators with keys, values, and key-value pairs.

In the following sections, you’ll learn about a few particularities of using in and not in with different built-in data types. You’ll start with lists, tuples, and range objects to kick things off.

Lists, Tuples, and Ranges

So far, you’ve coded a few examples of using the in and not in operators to determine if a given value is present in an existing list of values. For these examples, you’ve explicitly used list objects. So, you’re already familiar with how membership tests work with lists.

With tuples, the membership operators work the same as they would with lists:

>>>

>>> 5 in (2, 3, 5, 9, 7)
True

>>> 5 not in (2, 3, 5, 9, 7)
False

There are no surprises here. Both examples work the same as the list-focused examples. In the first example, the in operator returns True because the target value, 5, is in the tuple. In the second example, not in returns the opposite result.

For lists and tuples, the membership operators use a search algorithm that iterates over the items in the underlying collection. Therefore, as your iterable gets longer, the search time increases in direct proportion. Using Big O notation, you’d say that membership operations on these data types have a time complexity of O(n).

If you use the in and not in operators with range objects, then you get a similar result:

>>>

>>> 5 in range(10)
True

>>> 5 not in range(10)
False

>>> 5 in range(0, 10, 2)
False

>>> 5 not in range(0, 10, 2)
True

When it comes to range objects, using membership tests may seem unnecessary at first glance. Most of the time, you’ll know the values in the resulting range beforehand. But what if you’re using range() with offsets that are determined at runtime?

Consider the following examples, which use random numbers to determine offsets at runtime:

>>>

>>> from random import randint

>>> 50 in range(0, 100, randint(1, 10))
False

>>> 50 in range(0, 100, randint(1, 10))
False

>>> 50 in range(0, 100, randint(1, 10))
True

>>> 50 in range(0, 100, randint(1, 10))
True

On your machine, you might get different results because you’re working with random range offsets. In these specific examples, step is the only offset that varies. In real code, you could have varying values for the start and stop offsets as well.

For range objects, the algorithm behind the membership tests computes the presence of a given value using the expression (value - start) % step) == 0, which depends on the offsets used to create the range at hand. This makes membership tests very efficient when they operate on range objects. In this case, you’d say that their time complexity is O(1).

Remember that the target value in a membership test can be of any type. The test will check if that value is or isn’t in the target collection. For example, say that you have a hypothetical app where the users authenticate with a username and a password. You can have something like this:

# users.py

username = input("Username: ")
password = input("Password: ")

users = [("john", "secret"), ("jane", "secret"), ("linda", "secret")]

if (username, password) in users:
    print(f"Hi {username}, you're logged in!")
else:
    print("Wrong username or password")

This is a naive example. It’s unlikely that anyone would handle their users and passwords like this. But the example shows that the target value can be of any data type. In this case, you use a tuple of strings representing the username and the password of a given user.

Here’s how the code works in practice:

$ python users.py
Username: john
Password: secret
Hi john, you're logged in!

$ python users.py
Username: tina
Password: secret
Wrong username or password

In the first example, the username and password are correct because they’re in the users list. In the second example, the username doesn’t belong to any registered user, so the authentication fails.

In these examples, it’s important to note that the order in which the data is stored in the login tuple is critical because something like ("john", "secret") isn’t equal to ("secret", "john") in tuple comparison even if they have the same items.

In this section, you’ve explored examples that showcase the core behavior of membership operators with common Python built-in sequences. However, there’s a built-in sequence left. Yes, strings! In the next section, you’ll learn how membership operators work with this data type in Python.

Strings

Python strings are a fundamental tool in every Python developer’s tool kit. Like tuples, lists, and ranges, strings are also sequences because their items or characters are sequentially stored in memory.

You can use the in and not in operators with strings when you need to figure out if a given character is present in the target string. For example, say that you’re using strings to set and manage user permissions for a given resource:

>>>

>>> class User:
...     def __init__(self, username, permissions):
...         self.username = username
...         self.permissions = permissions
...

>>> admin = User("admin", "wrx")
>>> john = User("john", "rx")

>>> def has_permission(user, permission):
...     return permission in user.permissions
...

>>> has_permission(admin, "w")
True
>>> has_permission(john, "w")
False

The User class takes two arguments, a username and a set of permissions. To provide the permissions, you use a string in which w means that the user has write permission, r means that the user has read permission, and x implies execution permissions. Note that these letters are the same ones that you’d find in the Unix-style file-system permissions.

The membership test inside has_permission() checks whether the current user has a given permission or not, returning True or False accordingly. To do this, the in operator searches the permissions string to find a single character. In this example, you want to know if the users have write permission.

However, your permission system has a hidden issue. What would happen if you called the function with an empty string? Here’s your answer:

>>>

>>> has_permission(john, "")
True

Because an empty string is always considered a substring of any other string, an expression like "" in user.permissions will return True. Depending on who has access to your users’ permissions, this behavior of membership tests may imply a security breach in your system.

You can also use the membership operators to determine if a string contains a substring:

>>>

>>> greeting = "Hi, welcome to Real Python!"

>>> "Hi" in greeting
True
>>> "Hi" not in greeting
False

>>> "Hello" in greeting
False
>>> "Hello" not in greeting
True

For the string data type, an expression like substring in string is True if substring is part of string. Otherwise, the expression is False.

An important point to remember when using membership tests on strings is that string comparisons are case-sensitive:

>>>

>>> "PYTHON" in greeting
False

This membership test returns False because strings comparisons are case-sensitive, and "PYTHON" in uppercase isn’t present in greeting. To work around this case sensitivity, you can normalize all your strings using either the .upper() or .lower() method:

>>>

>>> "PYTHON".lower() in greeting.lower()
True

In this example, you use .lower() to convert the target substring and the original string into lowercase letters. This conversion tricks the case sensitivity in the implicit string comparison.

Generators

Generator functions and generator expressions create memory-efficient iterators known as generator iterators. To be memory efficient, these iterators yield items on demand without keeping a complete series of values in memory.

In practice, a generator function is a function that uses the yield statement in its body. For example, say that you need a generator function that takes a list of numbers and returns an iterator that yields square values from the original data. In this case, you can do something like this:

>>>

>>> def squares_of(values):
...     for value in values:
...         yield value ** 2
...

>>> squares = squares_of([1, 2, 3, 4])

>>> next(squares)
1
>>> next(squares)
4
>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

This function returns a generator iterator that yields square numbers on demand. You can use the built-in next() function to retrieve consecutive values from the iterator. When the generator iterator is completely consumed, it raises a StopIteration exception to communicate that no more values are left.

You can use the membership operators on a generator function like squares_of():

>>>

>>> 4 in squares_of([1, 2, 3, 4])
True
>>> 9 in squares_of([1, 2, 3, 4])
True
>>> 5 in squares_of([1, 2, 3, 4])
False

The in operator works as expected when you use it with generator iterators, returning True if the value is present in the iterator and False otherwise.

However, there’s something you need to be aware of when checking for membership on generators. A generator iterator will yield each item only once. If you consume all the items, then the iterator will be exhausted, and you won’t be able to iterate over it again. If you consume only some items from a generator iterator, then you can iterate over the remaining items only.

When you use in or not in on a generator iterator, the operator will consume it while searching for the target value. If the value is present, then the operator will consume all the values up to the target value. The rest of the values will still be available in the generator iterator:

>>>

>>> squares = squares_of([1, 2, 3, 4])

>>> 4 in squares
True

>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

In this example, 4 is in the generator iterator because it’s the square of 2. Therefore, in returns True. When you use next() to retrieve a value from square, you get 9, which is the square of 3. This result confirms that you no longer have access to the first two values. You can continue calling next() until you get a StopIteration exception when the generator iterator is exhausted.

Likewise, if the value isn’t present in the generator iterator, then the operator will consume the iterator completely, and you won’t have access to any of its values:

>>>

>>> squares = squares_of([1, 2, 3, 4])

>>> 5 in squares
False

>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

In this example, the in operator consumes squares completely, returning False because the target value isn’t in the input data. Because the generator iterator is now exhausted, a call to next() with squares as an argument raises StopIteration.

You can also create generator iterators using generator expressions. These expressions use the same syntax as list comprehensions but replace the square brackets ([]) with round brackets (()). You can use the in and not in operators with the result of a generator expression:

>>>

>>> squares = (value ** 2 for value in [1, 2, 3, 4])
>>> squares
<generator object <genexpr> at 0x1056f20a0>

>>> 4 in squares
True

>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
    ...
StopIteration

The squares variable now holds the iterator that results from the generator expression. This iterator yields square values from the input list of numbers. Generator iterators from generator expressions work the same as generator iterators from generator functions. So, the same rules apply when you use them in membership tests.

Another critical issue can arise when you use the in and not in operators with generator iterators. This issue can appear when you’re working with infinite iterators. The function below returns an iterator that yields infinite integers:

>>>

>>> def infinite_integers():
...     number = 0
...     while True:
...         yield number
...         number += 1
...

>>> integers = infinite_integers()
>>> integers
<generator object infinite_integers at 0x1057e8c80>

>>> next(integers)
0
>>> next(integers)
1
>>> next(integers)
2
>>> next(integers)
3
>>> next(integers)

The infinite_integers() function returns a generator iterator, which is stored in integers. This iterator yields values on demand, but remember, there will be infinite values. Because of this, it won’t be a good idea to use the membership operators with this iterator. Why? Well, if the target value isn’t in the generator iterator, then you’ll run into an infinite loop that’ll make your execution hang.

Dictionaries and Sets

Python’s membership operators also work with dictionaries and sets. If you use the in or not in operators directly on a dictionary, then it’ll check whether the dictionary has a given key or not. You can also do this check using the .keys() method, which is more explicit about your intentions.

You can also check if a given value or key-value pair is in a dictionary. To do these checks, you can use the .values() and .items() methods, respectively:

>>>

>>> likes = {"color": "blue", "fruit": "apple", "pet": "dog"}

>>> "fruit" in likes
True
>>> "hobby" in likes
False
>>> "blue" in likes
False

>>> "fruit" in likes.keys()
True
>>> "hobby" in likes.keys()
False
>>> "blue" in likes.keys()
False

>>> "dog" in likes.values()
True
>>> "drawing" in likes.values()
False

>>> ("color", "blue") in likes.items()
True
>>> ("hobby", "drawing") in likes.items()
False

In these examples, you use the in operator directly on your likes dictionary to check whether the "fruit", "hobby", and "blue" keys are in the dictionary or not. Note that even though "blue" is a value in likes, the test returns False because it only considers the keys.

Next up, you use the .keys() method to get the same results. In this case, the explicit method name makes your intentions much clearer to other programmers reading your code.

To check if a value like "dog" or "drawing" is present in likes, you use the .values() method, which returns a view object with the values in the underlying dictionary. Similarly, to check if a key-value pair is contained in likes, you use .items(). Note that the target key-value pairs must be two-item tuples with the key and value in that order.

If you’re using sets, then the membership operators work as they would with lists or tuples:

>>>

>>> fruits = {"apple", "banana", "cherry", "orange"}

>>> "banana" in fruits
True
>>> "banana" not in fruits
False

>>> "grape" in fruits
False
>>> "grape" not in fruits
True

These examples show that you can also check whether a given value is contained in a set by using the membership operators in and not in.

Now that you know how the in and not in operators work with different built-in data types, it’s time to put these operators into action with a couple of examples.

Putting Python’s in and not in Operators Into Action

Membership tests with in and not in are pretty common operations in programming. You’ll find these kinds of tests in many existing Python codebases, and you’ll use them in your code as well.

In the following sections, you’ll learn how to replace Boolean expressions based on the or operator with membership tests. Because membership tests can be quite common in your code, you’ll also learn how to make these tests more efficient.

Replacing Chained or Operators

Using a membership test to replace a compound Boolean expression with several or operators is a useful technique that allows you to simplify your code and make it more readable.

To see this technique in action, say that you need to write a function that takes a color name as a string and determines whether it’s a primary color. To figure this out, you’ll use the RGB (red, green, and blue) color model:

>>>

>>> def is_primary_color(color):
...     color = color.lower()
...     return color == "red" or color == "green" or color == "blue"
...

>>> is_primary_color("yellow")
False

>>> is_primary_color("green")
True

In is_primary_color(), you use a compound Boolean expression that uses the or operator to check if the input color is either red, green, or blue. Even though this function works as expected, the condition may be confusing and difficult to read and understand.

The good news is that you can replace the above condition with a compact and readable membership test:

>>>

>>> def is_primary_color(color):
...     primary_colors = {"red", "green", "blue"}
...     return color.lower() in primary_colors
...

>>> is_primary_color("yellow")
False

>>> is_primary_color("green")
True

Now your function uses the in operator to check whether the input color is red, green, or blue. Assigning the set of primary colors to a properly named variable like primary_colors also helps to make your code more readable. The final check is pretty clear now. Anyone reading your code will immediately understand that you’re trying to determine if the input color is a primary color according to the RGB color model.

If you look at the example again, then you’ll notice that the primary colors have been stored in a set. Why? You’ll find your answer in the following section.

Writing Efficient Membership Tests

Python uses a data structure called a hash table to implement dictionaries and sets. Hash tables have a remarkable property: looking for any given value in the data structure takes about the same time, no matter how many values the table has. Using Big O notation, you’ll say that value lookups in hash tables have a time complexity of O(1), which makes them super fast.

Now, what does this feature of hash tables have to do with membership tests on dictionaries and sets? Well, it turns out that the in and not in operators work very quickly when they operate on these types. This detail allows you to optimize your code’s performance by favoring dictionaries and sets over lists and other sequences in membership tests.

To have an idea of how much more efficient than a list a set can be, go ahead and create the following script:

# performance.py

from timeit import timeit

a_list = list(range(100_000))
a_set = set(range(100_000))

list_time = timeit("-1 in a_list", number=1, globals=globals())
set_time = timeit("-1 in a_set", number=1, globals=globals())

print(f"Sets are {(list_time / set_time):.2f} times faster than Lists")

This script creates a list of integer numbers with one hundred thousand values and a set with the same number of elements. Then the script computes the time that it takes to determine if the number -1 is in the list and the set. You know up front that -1 doesn’t appear in the list or set. So, the membership operator will have to check all the values before getting a final result.

As you already know, when the in operator searches for a value in a list, it uses an algorithm with a time complexity of O(n). On the other hand, when the in operator searches for a value in a set, it uses the hash table lookup algorithm, which has a time complexity of O(1). This fact can make a big difference in terms of performance.

Go ahead and run your script from the command line using the following command:

$ python performance.py
Sets are 1563.33 times faster than Lists

Although your command’s output may be slightly different, it’ll still show a significant performance difference when you use a set instead of a list in this specific membership test. With a list, the processing time will be proportional to the number of values. With a set, the time will be pretty much the same for any number of values.

This performance test shows that when your code is doing membership checks on large collections of values, you should use sets instead of lists whenever possible. You’ll also benefit from sets when your code performs several membership tests during its execution.

However, note that it’s not a good idea to convert an existing list into a set just to perform a few membership tests. Remember that converting a list into a set is an operation with O(n) time complexity.

Using operator.contains() for Membership Tests

The in operator has an equivalent function in the operator module, which comes in the standard library. The function is called contains(). It takes two arguments—a collection of values and a target value. It returns True if the input collection contains the target value:

>>>

>>> from operator import contains

>>> contains([2, 3, 5, 9, 7], 5)
True

>>> contains([2, 3, 5, 9, 7], 8)
False

The first argument to contains() is the collection of values, and the second argument is the target value. Note that the order of arguments differs from a regular membership operation, where the target value comes first.

This function comes in handy when you’re using tools like map(), or filter() to process iterables in your code. For example, say you have a bunch of Cartesian points stored as tuples inside a list. You want to create a new list containing only the points that aren’t over the coordinate axis. Using the filter() function, you can come up with the following solution:

>>>

>>> points = [
...     (1, 3),
...     (5, 0),
...     (3, 7),
...     (0, 6),
...     (8, 3),
...     (2, 0),
... ]

>>> list(filter(lambda point: not contains(point, 0), points))
[(1, 3), (3, 7), (8, 3)]

In this example, you use filter() to retrieve the points that don’t contain a 0 coordinate. To do this, you use contains() in a lambda function. Because filter() returns an iterator, you wrap up everything in a call to list() to convert the iterator into a list of points.

Even though the construct in the above example works, it’s quite complex because it implies importing contains(), creating a lambda function on top of it, and calling a couple of functions. You can get the same result using a list comprehension either with contains() or the not in operator directly:

>>>

>>> [point for point in points if not contains(point, 0)]
[(1, 3), (3, 7), (8, 3)]

>>> [point for point in points if 0 not in point]
[(1, 3), (3, 7), (8, 3)]

The above list comprehensions are shorter and arguably more readable than the equivalent filter() call from the previous example. They’re also less complex because you don’t need to create a lambda function or call list(), so you’re reducing the knowledge requirements.

Supporting Membership Tests in User-Defined Classes

Providing a .__contains__() method is the most explicit and preferred way to support membership tests in your own classes. Python will automatically call this special method when you use an instance of your class as the right operand in a membership test.

You’ll likely add a .__contains__() method only to classes that’ll work as collections of values. That way, the users of your class will be able to determine if a given value is stored in a specific instance of your class.

As an example, say that you need to create a minimal stack data structure to store values following the LIFO (last in, first out) principle. One requirement of your custom data structure is to support membership tests. So, you end up writing the following class:

# stack.py

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __contains__(self, item):
        return item in self.items

Your Stack class supports the two core functionalities of stack data structures. You can push a value to the top of the stack and pop a value from the top of the stack. Note that your data structure uses a list object under the hood to store and manipulate the actual data.

Your class also supports membership tests with the in and not in operators. To do this, the class implements a .__contains__() method that relies on the in operator itself.

To try out your class, go ahead and run the following code:

>>>

>>> from stack import Stack

>>> stack = Stack()
>>> stack.push(1)
>>> stack.push(2)
>>> stack.push(3)

>>> 2 in stack
True
>>> 42 in stack
False
>>> 42 not in stack
True

Your class fully supports the in and not in operators. Great job! You now know how to support membership tests in your own classes.

Note that if a given class has a .__contains__() method, then the class doesn’t have to be iterable for the membership operators to work. In the example above, Stack isn’t iterable, and the operators still work because they retrieve their result from the .__contains__() method.

There are at least two more ways to support membership tests in user-defined classes apart from providing a .__contains__() method. If your class has either an .__iter__() or a .__getitem__() method, then the in and not in operators also work.

Consider the following alternative version of Stack:

# stack.py

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __iter__(self):
        yield from self.items

The .__iter__() special method makes your class iterable, which is enough for membership tests to work. Go ahead and give it a try!

Another way to support membership tests is to implement a .__getitem__() method that handles indexing operations using zero-based integer indices in your classes:

# stack.py

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __getitem__(self, index):
        return self.items[index]

Python automatically calls the .__getitem__() method when you perform indexing operations on the underlying object. In this example, when you do stack[0], you’ll get the first item in the Stack instance. Python takes advantage of .__getitem__() to make the membership operators work correctly.

Conclusion

Now you know how to perform membership tests using Python’s in and not in operators. This type of test allows you to check if a given value is present in a collection of values, which is a pretty common operation in programming.

In this tutorial, you’ve learned how to:

  • Run membership tests using Python’s in and not in operators
  • Use the in and not in operators with different data types
  • Work with operator.contains(), the equivalent function to the in operator
  • Support in and not in in your own classes

With this knowledge, you’re good to go with membership tests using Python’s in and not in operators in your code.

Python has many built-in operators to perform various operations, among them membership operators “in” and “not in” are used to check if something specific is present in iterable or not. Python uses these operators frequently to make search operations easier and more efficient.

This tutorial will introduce you to the “in” and “not in” membership operators and give you several examples so that you can easily implement them in your Python program.

Python “in” Operator

Python “in” operator is used to check whether a specified value is a constituent element of a sequence like string, array, list, tuple, etc.

When used in a condition, the statement returns a Boolean result evaluating either True or False. When the specified value is found inside the sequence, the statement returns True. Whereas when it is not found, we get a False.

Now let us take an example to get a better understanding of the working of “in” operator.

Example:

#in operator working

list1= [1,2,3,4,5]
string1= "My name is AskPython"
tuple1=(11,22,33,44)

print(5 in list1) #True
print("is" in string1) #True
print(88 in tuple1) #False

Output:

Python In Output

Python in Output

Explanation:

  • Here we have initialised a list of integers list1, a string string1 and a tuple tuple1 with some values. Then we use the ‘in’ operator to check whether a value is a part of the above sequences or not.
  • In the output, the integer 5 in list1 evaluates into a True, which signifies that the value 5 is found inside the list in Python.
  • Similarly, using the “in” operator we also confirm the presence of the string “is” in string1.
  • But for the last case, the condition results in a False since 88 does not exist inside the sequence tuple1.

Python “not in” Operator

The “not in” operator in Python works exactly the opposite way as the “in” operator. It also checks the presence of a specified value inside a given sequence but its return values are totally opposite to that of the “in” operator.

When used in a condition with the specified value present inside the sequence, the statement returns False. Whereas when it is not, we get a True.

Example:

Let us take the previous example, just replacing “in” operator with the “not in”.

#not in operator working

list1= [1,2,3,4,5]
string1= "My name is AskPython"
tuple1=(11,22,33,44)

print(5 not in list1) #False
print("is" not in string1) #False
print(88 not in tuple1) #True

Output:

Not In Output

not in Output

As expected, the resultant output is the exact opposite of what we got earlier using the “in” operator.

Using the “in” and “not in” Operators in Python Dictionaries

Previously we discussed the working of the “in” and “not in” operators on different types of sequences. But dictionaries are not a sequence, they are not organized in the order of lists and tuples. Instead, dictionaries are indexed on the basis of keys., i.e., dictionaries use key-value pairs to store and fetch data. 

So do the above operators work on dictionaries? And if they do, how do they evaluate the condition?

Let us try to understand with an example.

Example:

#in and not in operator working on Dictionary

dict1 = {1: "one", 2: "two", 3: "three", 4: "four"}

print("one" in dict1)
print("one" not in dict1)

print(3 in dict1)
print(3 not in dict1)

print(5 in dict1)
print(5 not in dict1)

Output:

Using In And Not In On Dictionary

Using in And not in on Dictionary

Explanation:

  • Here we have initialised a dictionary dict1 with a certain set of keys and corresponding values.
  • In the output, “one” in dict1 evaluates into a False. Whereas, 3 in dict1 gives us True.
  • Meaning that the “in” operator looks for the element among the dictionary keys, not the values. Hence, similarly, the last statement 5 in dict1 also results in a False as it is not a key in the dictionary.

Here the “not in” operator also evaluates in the same way.

Conclusion

Let’s summarize what we’ve learned. In the Python programming language, there are two membership operators “in” and “not in” that can be used when you want to check whether a value or item is present in an iterator. “in” returns True if it is present and if it is not present it returns False, while “not in” is just the opposite, it returns False if it is present, and returns True if not present in a specific sequence. Hope you have successfully learned about “in” and “not in” operators through this tutorial.

Reference

  • How does the “in” and “not in” statement work in Python? – StackOverflow Question

In this article, we will learn about membership and identity operators in Python which are in, not in, is and is not operators. These operators are used to check if a given data element is part of a sequence or not and in the case of identity operator, whether a given element is of a certain type. Let’s start with the membership function first.

Whether you’re new to Python or just looking to refresh your knowledge, this article will provide a comprehensive guide to using membership and identity operators in Python.


Python Membership Operators

These operators help validate whether a given element is present in or is a member of the given sequence of data. This sequence of data can be a list, string or a tuple.

in Operator:

It checks whether the value is present in the sequence of data or not. It evaluates to true value if the element is present in the sequence and to false value if the element is absent from the sequence. Let’s take an example to understand its usage,

my_new_list = [1,2,3,'a']
# loop around the list
for i in my_new_list:
    print(i)

Output:

1
2
3
a

The in operator in the for loop allows the variable i to reference every element present in the list iterated by the loop. You must be thinking that the in operator is used to check if an element is present in a sequence or not, but what exactly is happening here? Well, the in operator performs differently when it is used in different contexts, i.e in a loop and in a conditional statement like if statement.

The statement x in y calls y.__contains__(x) if y has a __contains__ member function otherwise x in y will try to iterate through the y.__iter__() function to find x or it calls the y.__getitem__(x) if the iter method doesn’t exist.

The y.__getitem__(x) works similar to y is x[i] or y == x[i], where ‘i’ has to fulfill the condition of being a non-negative integer (index). If this condition on ‘i’ fails, it raises an IndexError exception.

But when we use in operator with the for loop, then it is a marker that separates loop-index variable from the iterable object. Hence we can say that the above example is not actually utilizing the in operator, so let’s take another example,

first_list = [1,2,3,'a']
second_list = ['a','b','c',10]
# loop around the list
for i in first_list:
    if i in second_list:
      print(i,"is present in both lists")

Output:

a is present in both lists

In the example above we can see the actual use of the in operator which is used to check if a given element is present in a sequence or not.

One important thing to remember is that empty strings are always considered to be a part/substring of a string. Here is an example to demonstrate the same:

'' in 'abc123'

Output:

True

not in Operator:

This operator checks for the absence of a value in a sequence. This is the exact opposite of the in operator. It evaluates to true when the element is not found or absent from the sequence and returns false when the element is found in the data sequence. The element to be searched for is the left operand and the sequence in which it is searched for is the right-hand operand.

my_new_list = [1,2,3, 'a']
special = 'Studytonight'
if special not in my_new_list:
    print('True')

Output:

True

special not in my_new_list returns the negation of the in operator. The if condition checks whether or not the variable special is present in the list. Since the element special isn’t present in the list, it returns true.


Python Identity Operators

These operators help in determining whether a value belongs to a certain class or a certain type, i.e they help in determining the identity of the object. It is useful in finding out the data type a variable holds. The identity of an object can be determined by using the id() function. For example,

list = [1,2,3,4,5]
print(id(list))

This will print a unique id representing the identity of that particular python object, in this case, a list.

is Operator:

It helps determine if a certain object belongs to a certain class/data type or if two operands are of the same object or not. We use this operator when in any condition we have to check for an object’s identity. If the type/class of the operand on either side of the is operator is same, it evaluates to a true value, else to a false value.

A basic example of using the is operator would be to match to objects, for example,

list1 = [1, 2, 3, 'a']
list2 = [10, 20, 30]
# using is operator
if list1 is list2:
    print("True")
else:
    print("False")

The above python script will print False because list1 and list2 are not same, they occupy different memory location and have different identity, we can check that using the id() function.

Let’s modify the above code and assign list1 to list2 and then use the is operator to check if its able to find out that both the objects are same,

list1 = [1, 2, 3, 'a']
list2 = list1

# using is operator
if list1 is list2:
    print("True")
else:
    print("False")

In this case, the output will be True, because list2 is also referencing the same list which is referenced by the list1 variable.

We can also use the is operator with other functions like the type() function to check whether two python objects are of same type or not.

my_new_list = [1, 2, 3, 'a']
my_new_tuple = (1, 2, 3, 'a')
type(my_new_list)
type(my_new_tuple)
if type(my_new_list) is type(my_new_tuple):
    print('They belong to same type')
else:
    print("They don't belong to same type")

Output:

They don't belong to same type.

Since the data structure list is not the same as the data structure tuple, hence the else block is executed.

is not Operator:

It behaves in the exact opposite way of the is operator. It checks whether values on either side of the is not operator are not same or same. If they are not same, it evaluates to a true value, else to a false value.

Again, we can use this with the type() function to compare data types of different python objects:

my_new_list = [1,2,3, 'a']
my_new_tuple = (1,2,3, 'a')
type(my_new_tuple)
if type(my_new_list) is not type(my_new_tuple):
    print('True!, They are not of the same type')
else:
    print("False, They are the same type")

Output:

True!, They are not of the same type

Since a tuple and a list are not the same and the is not operator checks for their inequality, it returns True.

Let’s take another example where we will compare two operands to see if they are the same object or not.

my_list_one = [1, 2, 3]
my_list_two = [1, 2, 3]
my_list_one is not my_list_two

Output:

True

This is because both the lists refer to different objects which are present at different locations in memory.


Conclusion

The membership and identity operators come in handy while looking to check for certain elements in a sequence of data and to check the identity of the data respectively. The identity operators can be used with the type() function for checking if a variable is of certain type or of certain class before performing any operation on it.


Frequently Asked Questions

1: What are membership and identity operators in Python?

Membership and identity operators in Python are used to test if a value is a member of a sequence or if two objects refer to the same object in memory. The membership operators are «in» and «not in», while the identity operators are «is» and «is not».

2: How does the «in» operator work in Python?

The «in» operator in Python returns «True» if a value is found in a sequence, and «False» if it is not found. For example, «5 in [1, 2, 3, 4, 5]» would return «True».

3: What is the difference between the «is» and «==» operators in Python?

The «is» operator in Python tests for object identity, meaning it checks if two objects refer to the same object in memory. The «==» operator, on the other hand, tests for equality, meaning it checks if two objects have the same value.

4: How can I use the «not in» operator in Python?

The «not in» operator in Python works similarly to the «in» operator but returns «True» if a value is NOT found in a sequence. For example, «5 not in [1, 2, 3, 4, 5]» would return «False».

You may also like:

  • Python String methods — split(), join() and replace()
  • Trigonometric Functions in Python — sin, cos, tan etc
  • How to copy elements from one list to another in Python
  • Converting Xlsx file To CSV file using Python

Понравилась статья? Поделить с друзьями:
  • Домик из бумажных трубочек 4 класс пошаговая инструкция
  • Катарин таблетки турецкие инструкция по применению на русском состав
  • Шумоизоляция в авто своими руками пошаговая инструкция
  • Основной принцип руководства работами по ликвидации чс
  • Стройтрансгаз руководство структура