2016-10-24 5 views
55

Почему вызов функции set уничтожает дубликатов, но разбор набора литералов не выполняется?Устанавливает литеральный результат, отличный результат от вызова функции функции

>>> x = Decimal('0') 
>>> y = complex(0,0) 
>>> set([0, x, y]) 
{0} 
>>> {0, x, y} 
{Decimal('0'), 0j} 

(Python 2.7.12. Возможно же первопричина, как для this подобного вопроса)

+0

родственные: [Dict/Set Синтаксического Order Консистенция] (https://stackoverflow.com/q/34623846/4279) – jfs

ответ

53

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

Поскольку 0 == xверно и0 == y верно, но x == y является ложным, поведение здесь действительно неопределенными, как набор предполагает, что x == y должно быть правдой, если первые два теста тоже были верны.

Если вы обратный список, переданный set(), то вы получите тот же результат, используя буквальным, так как порядок равенства тестирует изменения:

>>> set([y, x, 0]) 
set([0j, Decimal('0')]) 

и то же самое для реверсирования буквальным:

>>> {y, x, 0} 
set([0]) 

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

Пока 0 загружаются первого, остальные два объекта затем испытан против 0 уже в наборе. В тот момент, один из двух других объектов загружаются первым, тест равенства не удается, и вы получите добавлены два объекта:

>>> {y, 0, x} 
set([Decimal('0'), 0j]) 
>>> {x, 0, y} 
set([0j, Decimal('0')]) 

Это набор литералов добавлять элементы в обратном направлении является ошибка присутствует во всех версиях Python, которые поддерживают синтаксис , вплоть до Python 2.7.12 и 3.5.2. Это было недавно исправлено, см. issue 26020 (часть 2.7.13, 3.5.3 и 3.6, ни одна из которых еще не выпущена). Если вы посмотрите на 2.7.12, вы можете увидеть, что BUILD_SET in ceval.c считывает стек сверху вниз:

# oparg is the number of elements to take from the stack to add 
for (; --oparg >= 0;) { 
    w = POP(); 
    if (err == 0) 
     err = PySet_Add(x, w); 
    Py_DECREF(w); 
} 

в то время как байткод добавляет элементы в стек в обратном порядке (толкание 0 в стеке первого):

>>> from dis import dis 
>>> dis(compile('{0, x, y}', '', 'eval')) 
    2   0 LOAD_CONST    1 (0) 
       3 LOAD_GLOBAL    0 (x) 
       6 LOAD_GLOBAL    1 (y) 
       9 BUILD_SET    3 
      12 RETURN_VALUE 

Исправление состоит в том, чтобы читать элементы из стека в обратном порядке; Python 2.7.13 version использует PEEK() вместо POP()STACKADJ() удалить элементы из стека впоследствии):

for (i = oparg; i > 0; i--) { 
    w = PEEK(i); 
    if (err == 0) 
     err = PySet_Add(x, w); 
    Py_DECREF(w); 
} 
STACKADJ(-oparg); 

Равенство тестирования проблема имеет ту же причину, что и другой вопрос; класс Decimal() имеет некоторые проблемы с равенством с complex, который был исправлен в Python 3.2 (путем создания Decimal() support comparisons to complex and a few other numeric types it didn't support before).

+10

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

+7

@StevenRumbalski: Думаю, да, да. Почему «0j» не должен быть равен «Decimal ('0») ', когда' 0j == 0' истинно, и 'Decimal (' 0 ') == 0' также верен. Python 3.2 исправил это, обновив сравнение «Decimal». –

+2

'bytearray ('ab') == 'ab' == u'ab'' также нарушает транзитивность, но поскольку' bytearray' не хешируется, это не имеет никакого значения. – wim

7

Все дело в порядке, в котором построено множество, в сочетании с ошибкой, которую вы обнаружили с помощью your other question. Похоже, что литерал построен в обратном порядке для преобразования из списка.

>>> {0, x, y} 
set([0j, Decimal('0')]) 
>>> {y, x, 0} 
set([0]) 
+0

Ах, да, это проницательно. Как насчет ссылки источника cpython, которая показывает, как литерал анализируется справа налево? – wim

+0

@wim: Мне не удалось найти код для этого, только потому, что ошибка была недавно исправлена. –

+0

@wim, как забавно, как это звучит, я никогда не занимался spelunking через исходный код Python. –

Смежные вопросы