Порядок вставки в наборах (при разборе {})
Кто-то спросил здесь, почему при размещении 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).
Но это гарантировано? Или просто деталь реализации?
Ответы
Ответ 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}