Все мы знаем, что нужно использовать следующие две строки для проверки того, удовлетворяет ли переменная одному или нескольким равенствам:
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
с этого момента!
Читайте также:
- Регулярные выражения для извлечения информации о расходах из текстового файла
- Как проверить наличие файла или каталога в R, Python и Bash?
- 6 отборных практик для определения метода __init__ в Python
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Görkem Arslan: Stop Using “or” to Check Multiple Conditions in Python