Почему не стоит использовать or для проверки нескольких условий в Python

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

if number == 1 or number == 2 or number == 3 or number == 4:
do_smt()

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

На самом деле для реализации этой задачи существует более продвинутый прием. Он может оказаться лучшим решением, а может и не быть таковым. Все зависит от конкретной ситуации и от того, что вы подразумеваете под “лучшим решением”. Рассмотрим несколько подходов и проанализируем их сильные и слабые стороны.


Оператор in в Python

Ключевое слово in имеет два основных применения в Python.

1. Итерация по последовательности.

Пример:

seasons = ["winter", "spring", "summer", "autumn"]

for season in seasons:
print(season)

Результат:

winter
spring
summer
autumn

2. Проверка того, существует ли значение в последовательности (строке, кортеже, списке и т. д.) или нет. В зависимости от существования значения возвращается True или False.

Пример 1:

seasons = ["winter", "spring", "summer", "autumn"]

print("summer" in seasons)
print("july" in seasons)

Результат:

True
False

Пример 2:

Теперь посмотрим на подзаголовок этого раздела.

print("in" in "in operator in Python")

Результат:

True

Подведем итоги и посмотрим, есть ли элегантное решение этой задачи.


Используем in вместо or

Возьмем первый пример:

if number == 1 or number == 2 or number == 3 or number == 4:
do_smt()

Улучшим его с помощью ключевого слова in:

if number in [1, 2, 3, 4]:
do_smt()

Это означает, что если number существует в списке, то удовлетворяет условию и выполняется do_smt(). Другими словами, если number равно любому значению в списке, будет выполнена функция do_smt(). Обратите внимание, что слово “любому” в предыдущем предложении равнозначно слову “или”. Именно поэтому можно заменить or на in  —  оба ключевых слова имеют одинаковое назначение.


Почему и когда следует использовать in вместо or

Не правда ли, продвинутый вариант решения задачи читается довольно легко? Да, это действительно так! Мы можем легко следить за условиями. В случае нескольких условий использование ключевого слова in значительно повышает читабельность.

Например, чтобы проверить, является ли запрос операцией только для чтения в Django REST Framework (DRF), нужно просто выяснить, существует ли HTTP метод запроса (request.method) в SAFE_METHODS. Это является ничем иным, как кортежем, содержащим "GET", "HEAD" и "OPTIONS".

if request.method in permissions.SAFE_METHODS:
# Является ли это запросом только для чтения

Теперь явно напишем permissions.SAFE_METHODS, чтобы лучше во всем разобраться:

if request.method in ("GET", "HEAD", "OPTIONS"):
# Является ли это запросом только для чтения

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

В случае использования or нужно выполнить следующую запись: 

if request.method == "GET" or request.method == "HEAD" or request.method == "OPTIONS":
# Является ли это запросом только для чтения

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


Действительно ли это решение является лучшим?

Это зависит от того, что вы подразумеваете под словом “лучший”. Если вам важна читабельность, то, очевидно, лучше всего подойдет in. А если упор делается на производительность или потребление памяти?

Не стоит ограничивать себя только одним типом данных при использовании in. Рассмотрим возможности list, tuple и set.

Сравнение времени выполнения

Воспользуемся модулем timeit для измерения времени выполнения. Мы будем сравнивать 4 различных варианта использования: 1 (or) против 3 (in для list, tuple иset).

Такой порядок будет использоваться и дальше в статье:

in [] - list
in () - tuple
in {} - set
or

Выполним каждый из них 100000 раз. Также переименуем HTTP-метод запроса в method, потому что так короче.

import timeit

method = "GET"

print(timeit.timeit('method in ["GET", "HEAD", "OPTIONS"]', globals=globals(), number=100000))
print(timeit.timeit('method in ("GET", "HEAD", "OPTIONS")', globals=globals(), number=100000))
print(timeit.timeit('method in {"GET", "HEAD", "OPTIONS"}', globals=globals(), number=100000))
print(timeit.timeit('method == "GET" or method == "HEAD" or method == "OPTIONS"', globals=globals(), number=100000))

Результат:

0.0018657920008990914
0.0018152919947169721
0.00212829202064313
0.0020830420253332704

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

Примечание: параметр globals внутри timeit.timeit используется для доступа к переменным, определенным в глобальном пространстве имен. Таким образом, мы не оцениваем время, которое требуется для присвоения переменной method значения "GET”. В противном случае придется произвести следующее изменение:

print(timeit.timeit('method = "GET"; method in ["GET", "HEAD", "OPTIONS"]', globals=globals(), number=100000))

Теперь рассмотрим худший сценарий из всех возможных. На этот раз вместо первого условия будет выполнено последнее:

import timeit

method = "OPTIONS"

print(timeit.timeit('method in ["GET", "HEAD", "OPTIONS"]', globals=globals(), number=100000))
print(timeit.timeit('method in ("GET", "HEAD", "OPTIONS")', globals=globals(), number=100000))
print(timeit.timeit('method in {"GET", "HEAD", "OPTIONS"}', globals=globals(), number=100000))
print(timeit.timeit('method == "GET" or method == "HEAD" or method == "OPTIONS"', globals=globals(), number=100000))

Результат:

0.004029749979963526
0.0039225000073201954
0.0028425419877748936
0.008492207998642698

В этом случае sets показывает лучшие результаты, а or  —  худшие.

Сравнение потребления памяти

Мы можем использовать sys.getsizeof() для вычисления потребления памяти объектами:

import sys

print(sys.getsizeof(["GET", "HEAD", "OPTIONS"]))
print(sys.getsizeof(("GET", "HEAD", "OPTIONS")))
print(sys.getsizeof({"GET", "HEAD", "OPTIONS"}))

Результат:

120
64
216

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

Заключение

in  —  это стильный способ проверки нескольких условий. C его помощью вы можете сделать код более читабельным и удобным для дальнейшей поддержки. in можно использовать с tuples, потому что ключевое слово in эффективнее, когда речь идет о памяти и производительности. 

Вероятно, нелегко спрогнозировать время выполнения set по сравнению с другими контейнерами данных. Поэтому использование tuples более предсказуемо, чем применение set. Давайте же избавимся от or и будем использовать in с этого момента!

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Görkem Arslan: Stop Using “or” to Check Multiple Conditions in Python

Предыдущая статьяВся правда об использовании навигационной библиотеки Jetpack в модульных проектах
Следующая статьяИспытаем ИИ в решении логических задач