Яка різниця між цими двома рядками коду:
if not x == 'val':
і
if x != 'val':
Чи одна ефективніша за іншу?
Було б краще використовувати
if x == 'val':
pass
else:
Яка різниця між цими двома рядками коду:
if not x == 'val':
і
if x != 'val':
Чи одна ефективніша за іншу?
Було б краще використовувати
if x == 'val':
pass
else:
Відповіді:
Використовується disдля перегляду байт-коду, згенерованого для двох версій:
not ==
4 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 2 (==)
9 UNARY_NOT
10 RETURN_VALUE
!=
4 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 3 (!=)
9 RETURN_VALUE
Останній має менше операцій, і тому, ймовірно, буде дещо ефективнішим.
Було відзначено, в commments (спасибі, @Quincunx ) , що якщо у вас є if foo != barпроти if not foo == barкількість операцій точно так же, це тільки те , що COMPARE_OPзміни і POP_JUMP_IF_TRUEперемикається в POP_JUMP_IF_FALSE:
not ==:
2 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_TRUE 16
!=
2 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 3 (!=)
9 POP_JUMP_IF_FALSE 16
У цьому випадку, якщо б не було різниці в обсязі роботи, необхідної для кожного порівняння, навряд чи ви побачите різницю в продуктивності.
Однак зауважте, що дві версії не завжди будуть логічно однаковими , оскільки це залежатиме від реалізації __eq__та __ne__для об'єктів, про які йдеться. Відповідно до документації щодо моделі даних :
Між операторами порівняння не маються на увазі зв’язки. Істина
x==yне означає, щоx!=yце помилково.
Наприклад:
>>> class Dummy(object):
def __eq__(self, other):
return True
def __ne__(self, other):
return True
>>> not Dummy() == Dummy()
False
>>> Dummy() != Dummy()
True
Нарешті, і, мабуть, найголовніше: загалом там, де вони логічно однакові, x != yнабагато читабельніше, ніжnot x == y .
__eq__відповідає __ne__, розбивається на плоскість.
not x == yє ще одна інструкція. Коли я вклав код у if, виявилося, що вони обоє мають однакову кількість інструкцій, просто одна мала, POP_JUMP_IF_TRUEа друга POP_JUMP_IF_FALSE(це була єдина різниця між ними, окрім використання іншого COMPARE_OP). Коли я склав код без ifs, я отримав те, що у вас є.
==і !=не є взаємовиключними, - це SQL-подібна реалізація, що включає nullзначення. У SQL nullне повертається trueдо !=порівняно з будь-яким іншим значенням, тому реалізація python інтерфейсів SQL також може мати ту саму проблему.
not ==і !=, здається, це найцікавіша частина моєї відповіді! Я не думаю, що тут не варто зупинятися, якщо, чому і коли це має сенс - див. Наприклад, чому Python має __ne__метод оператора, а не просто __eq__?
@jonrsharpe чудово пояснює, що відбувається. Я думав, що я просто покажу різницю у часі, коли запускати кожен із трьох варіантів 1000000000 разів (достатньо, щоб показати невелику різницю).
Використовуваний код:
def a(x):
if x != 'val':
pass
def b(x):
if not x == 'val':
pass
def c(x):
if x == 'val':
pass
else:
pass
x = 1
for i in range(10000000):
a(x)
b(x)
c(x)
І результати профайлера cProfile:

Таким чином, ми можемо бачити, що між if not x == 'val':і між ними є різниця у хвилинах ~ 0.7% if x != 'val':. З них if x != 'val':найшвидший.
Однак, що найдивніше, ми можемо це побачити
if x == 'val':
pass
else:
насправді найшвидший і б'є if x != 'val':на ~ 0,3%. Це не дуже читабельно, але, мабуть, якби ви хотіли незначного поліпшення продуктивності, можна було б піти по цьому маршруту.
У першому Python повинен виконати ще одну операцію, ніж потрібно (замість того, щоб просто перевірити, що не дорівнює, він повинен перевірити, чи не правда, що він рівний, таким чином, ще одну операцію). Неможливо було б визначити різницю від одного виконання, але якщо запустити багато разів, друге було б більш ефективним. Я б взагалі використовував другий, але математично вони однакові
>>> from dis import dis
>>> dis(compile('not 10 == 20', '', 'exec'))
1 0 LOAD_CONST 0 (10)
3 LOAD_CONST 1 (20)
6 COMPARE_OP 2 (==)
9 UNARY_NOT
10 POP_TOP
11 LOAD_CONST 2 (None)
14 RETURN_VALUE
>>> dis(compile('10 != 20', '', 'exec'))
1 0 LOAD_CONST 0 (10)
3 LOAD_CONST 1 (20)
6 COMPARE_OP 3 (!=)
9 POP_TOP
10 LOAD_CONST 2 (None)
13 RETURN_VALUE
Тут ви можете побачити, що це not x == yще одна інструкція, ніж x != y. Тому різниця в продуктивності в більшості випадків буде дуже невеликою, якщо ви не робите мільйони порівнянь, і навіть тоді це, швидше за все, не стане причиною вузького місця.
Додаткове зауваження, оскільки інші відповіді відповідали на ваше питання здебільшого правильно, полягає в тому, що якщо клас визначає лише, __eq__()а не __ne__(), то ваш COMPARE_OP (!=)запустить __eq__()і заперечить його. У той час ваш третій варіант, ймовірно, буде крихітним, більш ефективним, але його слід враховувати лише в тому випадку, якщо вам ПОТРІБНА швидкість, оскільки це важко зрозуміти швидко.
Йдеться про ваш спосіб його читання. notОператор динамічний, тому ви можете його застосувати
if not x == 'val':
Але !=можна сприймати в кращому контексті як оператор, який робить протилежне тому, що ==робить.
notоператор динамічний" ?
Я хочу розкрити свій коментар з читабельністю вище.
Знову ж таки, я цілком погоджуюся з читабельністю, що переважає інші (неістотні) ефекти.
Я хотів би зазначити, що мозок інтерпретує "позитив" швидше, ніж "негативно". Наприклад, "стоп" проти "не йди" (досить хитрий приклад через різницю в кількості слів).
Тож надано вибір:
if a == b
(do this)
else
(do that)
є кращим для функціонально-еквівалентного:
if a != b
(do that)
else
(do this)
Менша читабельність / зрозумілість призводить до збільшення помилок. Можливо, не в початковому кодуванні, але (не настільки розумний, як ви!) Зміна обслуговування ...