Условные выражения: not x или x == False?

Рейтинг: 9Ответов: 3Опубликовано: 18.08.2023

В python существует два варианта проверки отрицательного условия:

if not x: 
if x == False:

Какой из этих двух вариантов лучше на предмет удобочитаемости и/или быстродействия?

Ответы

▲ 11Принят

Ответиь на этот вопрос нам поможет PEP 285:

It has been suggested that, in order to satisfy user expectations, for every x that is considered true in a Boolean context, the expression x == True should be true, and likewise if x is considered false, x == False should be true. In particular newbies who have only just learned about Boolean variables are likely to write

if x == True: ...

instead of the correct form,

if x: ...

Сокращенный перевод:

В частности, новички, которые только что узнали о логических переменных, скорее всего напишут if x == True: вместо правильной формы if x:

▲ 6

Прежде всего, вариант

if x == False:

очень некрасивый — объект False является одиночкой (singleton, единственным объектом), потому вместо оператора == (то же самое значение) рекомендуется применить оператор is (тот же самый объект):

if x is False:

Из PEP 8 — руководства по написанию кода на Python:

Сравнения с None должны обязательно выполняться с использованием операторов is или is not, а не с помощью операторов сравнения.

Не совсем правильный перевод. В английском оригинале стоит немножко другое:

Сравнения с одиночками как None должны обязательно выполняться с использованием операторов is или is not, а не с помощью операторов сравнения.


Теперь, команды

  • if x is False:
  • if not x:

не всегда идентичны - в команде if например пустой словарь вычисляется как False:

x = {}

if x is False:
    print("1. x is False")    # не выполнится

if not x:
    print("2. not x")         # выполнится

Ну и теперь к вашим вопросам:

  1. Удобочитаемость — сравните:

    • if (option in (1, 5, 6)) is False: и
    • if not option in (1, 5, 6):

    Удобочитаемость зависит от того, кто это читает.;-) Для меня это второй вариант.

  2. Быстродействие:

    option = 4
    
    %timeit if (option in (1, 5, 6)) is False: pass
    %timeit if not option in (1, 5, 6): pass
    
    161 ns ± 3.39 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
    139 ns ± 1.88 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
    

    Второй вариант чуть-чуть быстрее. Но не убедительно. По моему, интерпретатор Питона просто заранее не обнаружил совпадение, не оптимизировал первый вариант.

▲ 5

Прогнав такую проверку:

from time import perf_counter
from random import randint


def eq_compare(x):
    return x == False


def not_compare(x):
    return not x


lst = [[], {}, (), 1, 2, 3, True, None] + [randint(0, 10_000) for _ in range(1000)]

s = perf_counter()
for i in lst * 1000:
    eq_compare(i)
print(perf_counter() - s)

s = perf_counter()
for i in lst * 100:
    not_compare(i)
print(perf_counter() - s)

Скорость проверки через оператор not выше, чем через оператор равенства в 10+ раз. Вот несколько замеров (попарно):

== : 0.11482529999921098

not: 0.009857600030954927

== : 0.12146590003976598

not: 0.009981999988667667

== : 0.11241040000459179

not: 0.01083189999917522

Считаю, что медленность проверки через == обусловлена динамической типизацией - труднее динамически конвертировать типы в bool, так ещё потом и значения сравнивать.