Почему выражение 0 <0 == 0 возвращает False в Python?

136

Глядя на Queue.py в Python 2.6, я обнаружил, что эта конструкция показалась мне немного странной:

def full(self):
    """Return True if the queue is full, False otherwise
    (not reliable!)."""
    self.mutex.acquire()
    n = 0 < self.maxsize == self._qsize()
    self.mutex.release()
    return n

Если maxsize0, очередь никогда не заполнится.

Мой вопрос, как это работает в этом случае? Как 0 < 0 == 0считается ложным?

>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True
Марсело Сантос
источник
0 <True равен False в питоне?
Марино Шимич
3
@Marino Šimić: Из второго примера, показанного в вопросе ОП, >>> (0) < (0 == 0)это явно не так.
Мартино
3
Одна из причин, по которой вы не должны писать код, как n = 0 < self.maxsize == self._qsize()прежде всего, на любом языке. Если вашим глазам приходится несколько раз метаться по линии вперед и назад, чтобы понять, что происходит, это не очень хорошо написанная строка. Просто разбейте его на несколько строк.
BlueRaja - Дэнни Пфлугхофт
2
@Blue: Я согласен с тем, что такое сравнение не писать таким образом, но разделение его на отдельные строки будет немного запутанным для двух сравнений. Я надеюсь, что вы имеете в виду, разделить его на отдельные сравнения. ;)
Джефф Меркадо
2
@Blue: я не писал, это в Python 2.6. Я просто пытался понять, что происходит.
Марсело Сантос

Ответы:

113

Я полагаю, что в Python есть особая обработка случаев для последовательностей реляционных операторов, чтобы было легко выразить сравнения диапазонов. Гораздо приятнее говорить, 0 < x <= 5чем говорить (0 < x) and (x <= 5).

Это так называемые цепные сравнения . И это ссылка на документацию для них.

В других случаях, о которых вы говорите, скобки заставляют один оператор отношения применяться перед другим, поэтому они больше не являются цепочечными сравнениями. А так Trueи Falseимеют значение как целые числа вы получите ответы , которые вы делаете из версий скобок.

всевозможный
источник
интересно попробовать некоторые из этих сравнений и указать int () и bool (). Я понял, что bool () любого ненулевого значения равен 1. Полагаю, я бы никогда не попытался напрямую указать что-либо, кроме bool (0) или bool (1), перед этим мысленным экспериментом
j_syk
Вы намеревались сделать ссылку на этот раздел? docs.python.org/2/reference/expressions.html#comparisons
Тавнаб
@tavnab - Да. Я постараюсь не забыть исправить это. Я также собираюсь проверить историю редактирования. Это не похоже на ошибку, которую я сделал бы. 😕
всевозможный
42

Так как

(0 < 0) and (0 == 0)

есть False. Вы можете объединять операторы сравнения, и они автоматически расширяются в парные сравнения.


РЕДАКТИРОВАТЬ - разъяснение о True и False в Python

В Python Trueи Falseесть только экземпляры bool, которые являются подклассом int. Другими словами, на Trueсамом деле это просто 1.

Дело в том, что вы можете использовать результат логического сравнения точно так же, как целое число. Это приводит к запутанным вещам, таким как

>>> (1==1)+(1==1)
2
>>> (2<1)<1
True

Но это произойдет, только если вы заключите сравнения в скобки, чтобы они были оценены первыми. В противном случае Python расширит операторы сравнения.

Katriel
источник
2
Я видел интересное использование булевых значений, используемых вчера как целые числа. Выражение 'success' if result_code == 0 else 'failure'можно переписать так ('error', 'success')[result_code == 0], как до этого я никогда не видел логическое значение, используемое для выбора элемента в списке / кортеже.
Эндрю Кларк
'bool' был добавлен около Python 2.2.
MRAB
18

Странное поведение, которое вы испытываете, происходит от способности питонов связывать условия. Поскольку он находит 0 не меньше 0, он решает, что все выражение оценивается как ложное. Как только вы разбиваете это на отдельные условия, вы меняете функциональность. Первоначально это, по сути, тестирование a < b && b == cдля вашего первоначального утверждения a < b == c.

Другой пример:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True
Тайлер
источник
1
OMG, a < b && b == cтоже самое, что и a < b == cOO
Кирилл Киров
9
>>> 0 < 0 == 0
False

Это цепное сравнение. Он возвращает истину, если каждое попарное сравнение в свою очередь истинно. Это эквивалентно(0 < 0) and (0 == 0)

>>> (0) < (0 == 0)
True

Это эквивалентно тому, 0 < Trueчто оценивается как True.

>>> (0 < 0) == 0
True

Это эквивалентно тому, False == 0что оценивается как True.

>>> 0 < (0 == 0)
True

Эквивалент, 0 < Trueкоторому, как указано выше, присваивается значение True.

Дэвид Хеффернан
источник
7

Глядя на разборку (байты коды) , то понятно , почему 0 < 0 == 0это False.

Вот анализ этого выражения:

>>>import dis

>>>def f():
...    0 < 0 == 0

>>>dis.dis(f)
  2      0 LOAD_CONST               1 (0)
         3 LOAD_CONST               1 (0)
         6 DUP_TOP
         7 ROT_THREE
         8 COMPARE_OP               0 (<)
        11 JUMP_IF_FALSE_OR_POP    23
        14 LOAD_CONST               1 (0)
        17 COMPARE_OP               2 (==)
        20 JUMP_FORWARD             2 (to 25)
   >>   23 ROT_TWO
        24 POP_TOP
   >>   25 POP_TOP
        26 LOAD_CONST               0 (None)
        29 RETURN_VALUE

Обратите внимание на строки 0-8: эти строки проверяют, 0 < 0что возвращается Falseв стек Python.

Теперь обратите внимание на строку 11: JUMP_IF_FALSE_OR_POP 23 это означает, что, если 0 < 0return возвращает Falseпереход к строке 23.

Теперь, 0 < 0есть False, поэтому выполняется переход, который оставляет в стеке значение, Falseкоторое является возвращаемым значением для всего выражения 0 < 0 == 0, даже если == 0часть даже не проверяется.

Итак, в заключение, ответ, как сказано в других ответах на этот вопрос. 0 < 0 == 0имеет особое значение. Компилятор оценивает это двумя терминами: 0 < 0и 0 == 0. Как и в случае любых сложных логических выражений andмежду ними, если первое не удается, то второе даже не проверяется.

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

Sata
источник
Разве не будет проще разобраться со спецификацией, чем пересмотреть одну конкретную реализацию?
Дэвид Хеффернан
Это короткий ответ. Я считаю, что это зависит от вашей личности. Если вы относитесь к представлению «черного ящика» и предпочитаете получать ответы из спецификаций и документации, тогда этот ответ вас только смущает. Если вам нравится копаться и раскрывать внутренности вещей, тогда этот ответ для вас. Ваше замечание о том, что этот реверс-инжиниринг имеет отношение только к одной конкретной реализации, является правильным и должно быть указано, но это не было целью этого ответа. Это демонстрация того, как легко в python взглянуть «под капот» для тех, кто достаточно любопытен.
Сата
1
Проблема с реверс-инжинирингом заключается в отсутствии предсказательной силы. Это не способ выучить новый язык.
Дэвид Хеффернан
Еще одна вещь, которую я должен добавить, заключается в том, что спецификации и документация не всегда полны и в большинстве случаев не дают ответов для таких конкретных случаев. Тогда, я считаю, не нужно бояться исследовать, исследовать и углубляться настолько, насколько необходимо, чтобы получить ответы.
SatA
2

Как уже упоминалось, x comparison_operator y comparison_operator zэто синтаксический сахар (x comparison_operator y) and (y comparison_operator z)с бонусом, который у оценивается только один раз.

Таким образом, ваше выражение 0 < 0 == 0действительно (0 < 0) and (0 == 0), которое оценивает, False and Trueчто справедливо False.

доктор джимбоб
источник
2

может быть, эта выдержка из документации может помочь:

Это так называемые методы «расширенного сравнения», и они называются операторами сравнения в предпочтении __cmp__()ниже. Соответствие между символами операторов и именами методов выглядят следующим образом : x<yзвонки x.__lt__(y), x<=yзвонки x.__le__(y), x==yзвонки x.__eq__(y), x!=yи x<>y вызов x.__ne__(y), x>yвызовы x.__gt__(y)и x>=yвызовы x.__ge__(y).

Богатый метод сравнения может вернуть синглтон NotImplemented если он не реализует операцию для данной пары аргументов. По договоренности Falseи Trueвозвращаются для успешного сравнения. Однако эти методы могут возвращать любое значение, поэтому, если оператор сравнения используется в логическом контексте (например, в условии оператора if), Python вызовет bool()значение, чтобы определить, является ли результат истинным или ложным.

Не существует подразумеваемых отношений между операторами сравнения. Истина x==yне подразумевает, что x!=y это ложь. Соответственно при определении __eq__() следует также определить, __ne__()чтобы операторы вели себя так, как ожидалось. См. Параграф о __hash__()некоторых важных заметках о создании хешируемых объектов, которые поддерживают пользовательские операции сравнения и могут использоваться в качестве словарных ключей.

Не существует версий этих методов с переменным аргументом (которые будут использоваться, когда левый аргумент не поддерживает операцию, а правый аргумент поддерживает); скорее, __lt__()и __gt__() являются отражением друг друга, __le__() и __ge__()являются отражением друг друга, __eq__()и __ne__() являются их собственным отражением.

Аргументы в пользу богатых методов сравнения никогда не приводятся.

Это были сравнения, но так как вы сравниваете цепочки вы должны знать, что:

Сравнения могут быть связаны произвольно, например, x < y <= z эквивалентно x < y and y <= z, за исключением того, что y вычисляется только один раз (но в обоих случаях z вообще не оценивается, когда x <y оказывается ложным).

Формально, если a, b, c, ..., y, z являются выражениями и op1, op2, ..., opN являются операторами сравнения, то a op1 b op2 c ... y opN z эквивалентен op1 b и b op2 c и ... y opN z, за исключением того, что каждое выражение оценивается не более одного раза.

Марино Шимич
источник
1

Вот оно, во всей красе.

>>> class showme(object):
...   def __init__(self, name, value):
...     self.name, self.value = name, value
...   def __repr__(self):
...     return "<showme %s:%s>" % (self.name, self.value)
...   def __cmp__(self, other):
...     print "cmp(%r, %r)" % (self, other)
...     if type(other) == showme:
...       return cmp(self.value, other.value)
...     else:
...       return cmp(self.value, other)
... 
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>> 
SingleNegationElimination
источник
0

Я думаю, что Python делает это странно между магией. То же, что и 1 < 2 < 3средство 2, находится между 1 и 3.

В этом случае, я думаю, что это делает [middle 0] больше, чем [left 0] и равно [right 0]. Средний 0 не больше левого 0, поэтому он оценивается как ложный.

mpen
источник