Подтвердить что ты не робот

Порядок вставки в наборах (при разборе {})

Кто-то спросил здесь, почему при размещении 1 и True в set поддерживается только 1.

Это, конечно, потому что 1==True. Но в каких случаях 1 сохраняется и в каких случаях сохраняется True?

Посмотрим:

передав list, чтобы построить set вместо использования обозначения set:

>>> set([True,1])
{True}
>>> set([1,True])
{1}

кажется логичным: set выполняет итерацию во внутреннем списке и не добавляет второй элемент, потому что он равен первому элементу (обратите внимание, что set([True,1]) не может дать 1, потому что set не может знать, что внутри списка. Это может быть даже не list, но повторяемый)

Теперь с использованием обозначения set:

>>> {True,1}
{1}
>>> {1,True}
{True} 

Кажется, что в этом случае список элементов обрабатывается в обратном порядке (тестируется на Python 2.7 и Python 3.4).

Но это гарантировано? Или просто деталь реализации?

4b9b3361

Ответ 1

Порядок, в котором будут вставлены элементы в установочном литерале, не гарантируется спецификацией языка. Тем не менее, Python 3.6 был изменен так, чтобы он имел ожидаемый левый-правый порядок оценки. Для получения полной информации об этом изменении, issue, а также commit, который внес изменения в порядок вставки.


Чтобы описать изменение более подробно, построение набора литерала {True, 1} запускает код операции BUILD_SET (с oparg равным 2) после первого нажатия указателей на True и 1 на внутреннюю виртуальную машину стек.

В Python 3.4 BUILD_SET использует следующий цикл для вставки элементов в набор (обратите внимание, что в нашем случае oparg равно 2):

while (--oparg >= 0) {
    PyObject *item = POP();
    if (err == 0)
        err = PySet_Add(set, item);
        Py_DECREF(item);

Так как 1 был добавлен в последний стек, он сначала выскользнул и стал первым объектом, вставленным в набор.

В более новых версиях Python (например, 3.6), opcode BUILD_SET использует PEEK вместо POP:

for (i = oparg; i > 0; i--) {
    PyObject *item = PEEK(i);
    if (err == 0)
        err = PySet_Add(set, item);
        Py_DECREF(item);

PEEK(i) извлекает элемент я th в стек, поэтому для {True, 1} объект True добавляется к набору в первую очередь.

Ответ 2

Порядок слева и справа на дисплее отображается документация:

его элементы оцениваются слева направо и добавляются к заданному объекту

Пример:

>>> def f(i, seq="left to right".split()): print(seq[i])
>>> {f(0), f(1), f(2)}
left
to
right
{None}

Следовательно, {1, True} эффективно:

>>> S = set()
>>> S.add(1)
>>> S.add(True) # no effect according to docs
>>> S
{1}

Набор может содержать только один из True или 1, поскольку они являются дубликатами с точки зрения set:

>>> hash(1) == hash(True)
True
>>> 1 == True
True

На Python 3 гарантировано, что 1 == True. См. Является ли` False == 0 и True == 1 в Python деталь реализации или это гарантируется языком?:

Booleans: они представляют значения истинности False и True [...] Булевы значения ведут себя как значения 0 и 1, соответственно, почти во всех контекстах, исключение состоит в том, что при преобразовании в строку строки "False" или "True", соответственно.

Если {1, True} печатает {True}, то это ошибка ( "порядок оценки set_display не соответствует документированному поведению" ). Выход должен быть таким же, как set([1, True]). Он работает как ожидалось ({1}) в последних версиях pypy, jython и cpython 2.7.13+, 3.5.3+.

Ответ 3

Из одной из последних версий dict сохраняет порядок как побочный эффект детали реализации. В 3.7 это поведение может быть гарантировано. Возможно, это также повлияло на набор литералов.

Python 3.6.2:

>>> {True,1}
{True}
>>> {1,True}
{1}
>>> set([True,1])
{True}
>>> set([1,True])
{1}