Почему в Python «0, 0 == (0, 0)» равно «(0, False)»?

118

В 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

Почему результаты двух подходов различаются? По-разному ли оператор равенства обрабатывает кортежи?

Петр Закжевский
источник

Ответы:

156

Первые два выражения анализируются как кортежи:

  1. (0, 0) == 0(что есть False), за которым следует0
  2. 0, за которым следует 0 == (0, 0)(что по-прежнему Falseтак).

Выражения разделяются таким образом из-за относительного приоритета разделителя запятой по сравнению с оператором равенства: Python видит кортеж, содержащий два выражения, одно из которых является проверкой равенства, а не проверкой равенства между двумя кортежами.

Но во втором наборе операторов a = 0, 0 не может быть кортежем. Кортеж - это набор значений, и в отличие от теста на равенство присваивание не имеет значения в Python. Присваивание - это не выражение, а утверждение; у него нет значения, которое можно было бы включить в кортеж или любое другое окружающее выражение. Если вы попробуете что-то вроде (a = 0), 0принудительной интерпретации как кортежа, вы получите синтаксическую ошибку. Это оставляет присвоение кортежа переменной, которое можно было бы сделать более явным, написав его, a = (0, 0)как единственно допустимую интерпретацию a = 0, 0.

Таким образом, даже без круглых скобок в присвоении aкак ему, так и bполучению присваивается значение (0,0), поэтому так оно и a == bесть True.

Марк Рид
источник
17
Я бы сказал, что оператор запятой имеет более низкий приоритет, чем равенство, поскольку оценка равенства предшествует оценке оператора запятой: равенство имеет более высокий приоритет, чем оператор запятой. Но это всегда источник путаницы; просто хотел указать на то, что другие источники могут изменить ситуацию.
tomsmeding 01
2
Вы можете избежать путаницы в словах «низший / высший», вместо этого сказав, что « ,связывает меньше» ==.
amalloy 01
4
Запятая - это не оператор docs.python.org/3.4/faq/…
Chris_Rands 01
48
Документы могут утверждать, что они хотят, но это не имеет значения. Вы можете написать синтаксический анализатор, чтобы каждый оператор получал свое собственное производство и нигде в реализации не было явного «приоритета», но это не мешает этим синтаксическим единицам быть операторами. Вы можете переопределить «оператор» некоторым способом, зависящим от реализации. , что, по-видимому, и сделали в Python, но это не меняет смысла этого термина. Запятая фактически является оператором, который производит кортежи. Его функциональность проявляется, например, в том, как скобки влияют на его относительный приоритет.
Марк Рид
68

То, что вы видите во всех трех экземплярах, является следствием грамматической спецификации языка и того, как токены, встречающиеся в исходном коде, анализируются для создания дерева синтаксического анализа.

Взгляд на этот низкоуровневый код должен помочь вам понять, что происходит под капотом. Мы можем взять эти операторы 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.

cs95
источник
20

Другой способ объяснить проблему: вы, вероятно, знакомы со словарными литералами.

{ "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
zwol
источник
17

Добавление пары скобок вокруг порядка выполнения действий может помочь вам лучше понять результаты:

# 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.

Димитрис Фасаракис Хиллиард
источник
6

В первом Python создает кортеж из двух вещей:

  1. Выражение (0, 0) == 0, которое оценивается какFalse
  2. Постоянная 0

Во втором - наоборот.

Kindall
источник
0

посмотрите на этот пример:

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) в примере.

Эмад Саейди
источник