Почему в Python 0, 0 == (0, 0) равно (0, False)
В Python (я проверил только с Python 3.6, но я считаю, что он должен сохраняться и для многих предыдущих версий):
(0, 0) == 0, 0 # results in a two element tuple: (False, 0)
0, 0 == (0, 0) # results in a two element tuple: (0, False)
(0, 0) == (0, 0) # results in a boolean True
Но:
a = 0, 0
b = (0, 0)
a == b # results in a boolean True
Почему результат отличается от двух подходов? Оператор равенства обрабатывает кортежи по-разному?
Ответы
Ответ 1
Первые два выражения анализируются как кортежи:
-
(0, 0) == 0
(который есть False
), за которым следует 0
-
0
, а затем 0 == (0, 0)
(который по-прежнему False
).
Выражения разделены таким образом из-за относительного приоритета разделителя запятой по сравнению с оператором равенства: Python видит кортеж, содержащий два выражения, один из которых является тестом равенства, вместо теста равенства между двумя кортежами.
Но в вашем третьем примере a = 0, 0
не может быть кортежем. Кортеж представляет собой набор значений, и в отличие от теста равенства присваивание не имеет значения в Python. Назначение не является выражением, а выражением; он не имеет значения, которое может быть включено в кортеж или любое другое окружающее выражение. Если вы попытались что-то вроде (a = 0), 0
, чтобы заставить интерпретацию как кортеж, вы получите синтаксическую ошибку. Это оставляет назначение кортежа переменной - что может быть сделано более явным, написав его a = (0, 0)
- как единственную допустимую интерпретацию a = 0, 0
.
Ответ 2
То, что вы видите во всех трех случаях, является следствием грамматической спецификации языка и того, как токены, встречающиеся в исходном коде, анализируются для создания дерева разбора.
Взглянув на этот код низкого уровня, вы сможете понять, что происходит под капотом. Мы можем взять эти операторы python, преобразовать их в байт-код и затем декомпилировать их с помощью модуля dis
:
Случай 1: (0, 0) == 0, 0
>>> dis.dis(compile("(0, 0) == 0, 0", '', 'exec'))
1 0 LOAD_CONST 2 ((0, 0))
3 LOAD_CONST 0 (0)
6 COMPARE_OP 2 (==)
9 LOAD_CONST 0 (0)
12 BUILD_TUPLE 2
15 POP_TOP
16 LOAD_CONST 1 (None)
19 RETURN_VALUE
(0, 0)
сначала сравнивается с 0
сначала и оценивается как False
. Затем создается кортеж с этим результатом и последним 0
, так что вы получите (False, 0)
.
Случай 2: 0, 0 == (0, 0)
>>> dis.dis(compile("0, 0 == (0, 0)", '', 'exec'))
1 0 LOAD_CONST 0 (0)
3 LOAD_CONST 0 (0)
6 LOAD_CONST 2 ((0, 0))
9 COMPARE_OP 2 (==)
12 BUILD_TUPLE 2
15 POP_TOP
16 LOAD_CONST 1 (None)
19 RETURN_VALUE
Кортеж создается с 0
в качестве первого элемента. Для второго элемента такая же проверка выполняется, как в первом случае, и оценивается как False
, поэтому вы получите (0, False)
.
Случай 3: (0, 0) == (0, 0)
>>> dis.dis(compile("(0, 0) == (0, 0)", '', 'exec'))
1 0 LOAD_CONST 2 ((0, 0))
3 LOAD_CONST 3 ((0, 0))
6 COMPARE_OP 2 (==)
9 POP_TOP
10 LOAD_CONST 1 (None)
13 RETURN_VALUE
Здесь, как вы видите, вы просто сравниваете эти два (0, 0)
кортежа и возвращаете True
.
Ответ 3
Другой способ объяснить проблему: вы, вероятно, знакомы со словарными литералами
{ "a": 1, "b": 2, "c": 3 }
и литералы массива
[ "a", "b", "c" ]
и литералы кортежа
( 1, 2, 3 )
но то, что вы не понимаете, заключается в том, что в отличие от словарных и литературных литералов круглые скобки, которые вы обычно видите вокруг литерала кортежа, не являются частью литерального синтаксиса. Литеральный синтаксис кортежей - это просто последовательность выражений, разделенных запятыми:
1, 2, 3
( "exprlist" на языке формальной грамматики для Python).
Теперь, что вы ожидаете от литерала массива
[ 0, 0 == (0, 0) ]
для оценки? Вероятно, это похоже на то, что он должен быть таким же, как
[ 0, (0 == (0, 0)) ]
который, конечно, оценивается до [0, False]
. Аналогично, с явно заключенным в скобки литералом
( 0, 0 == (0, 0) )
неудивительно получить (0, False)
. Но скобки необязательны:
0, 0 == (0, 0)
- одно и то же. И вот почему вы получаете (0, False)
.
Если вам интересно, почему круглые скобки вокруг экземпляра кортежа являются необязательными, это во многом потому, что было бы неприятно записывать назначения деструктурирования таким образом:
(a, b) = (c, d) # meh
a, b = c, d # better
Ответ 4
Добавление нескольких круглых скобок вокруг порядка выполнения действий может помочь вам лучше понять результаты:
# Build two element tuple comprising of
# (0, 0) == 0 result and 0
>>> ((0, 0) == 0), 0
(False, 0)
# Build two element tuple comprising of
# 0 and result of (0, 0) == 0
>>> 0, (0 == (0, 0))
(0, False)
# Create two tuples with elements (0, 0)
# and compare them
>>> (0, 0) == (0, 0)
True
Запятая используется для разделения выражений (с помощью круглых скобок, конечно, мы можем заставить другое поведение). При просмотре фрагментов, которые вы указали, запятая ,
отделяет ее и определяет, какие выражения будут оцениваться:
(0, 0) == 0 , 0
#-----------|------
expr 1 expr2
Кортеж (0, 0)
также может быть разбит аналогичным образом. Запятая разделяет два выражения, содержащие литералы 0
.
Ответ 5
В первом Python создает кортеж из двух вещей:
- Выражение
(0, 0) == 0
, которое оценивается как False
- Постоянная
0
Во втором - наоборот.
Ответ 6
посмотрите на этот пример:
r = [1,0,1,0,1,1,0,0,0,1]
print(r==0,0,r,1,0)
print(r==r,0,1,0,1,0)
тогда результат:
False 0 [1, 0, 1, 0, 1, 1, 0, 0, 0, 1] 1 0
True 0 1 0 1 0
тогда сравнение только с первым числом (0 и r) в примере.