Почему кортежи, построенные из разных инициализированных множеств, равны?

Я ожидал следующие два набора:

>>> x = tuple(set([1, "a", "b", "c", "z", "f"]))
>>> y = tuple(set(["a", "b", "c", "z", "f", 1]))

сравнить неравные, но они этого не делают:

>>> x == y
>>> True

Почему это?

Ответ 1

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

>>> x = set([1, "a", "b", "c", "z", "f"])
>>> y = set(["a", "b", "c", "z", "f", 1])
>>> x
{1, 'z', 'a', 'b', 'c', 'f'}
>>> y
{1, 'z', 'a', 'b', 'c', 'f'}
>>> x == y
True

Однако, не всегда бывает, что кортежи (или другие упорядоченные коллекции), построенные из двух равных множеств, равны.

Фактически, результат вашего сравнения иногда True, а иногда False, по крайней мере, в Python >= 3.3. Тестирование следующего кода:

# compare.py
x = tuple(set([1, "a", "b", "c", "z", "f"]))
y = tuple(set(["a", "b", "c", "z", "f", 1]))
print(x == y)

... тысячу раз:

$ for x in {1..1000}
> do
>   python3.3 compare.py
> done | sort | uniq -c
147 False
853 True

Это связано с тем, что с Python 3.3 хеш-значения строк, байтов и datetime рандомизированы в результате исправления безопасности. В зависимости от того, что такое хеши, может произойти "столкновение", что будет означать, что элементы заказа хранятся в базовом массиве (и, следовательно, порядок итерации), зависят от порядка вставки.

Здесь соответствующий бит из документов:

Улучшения безопасности:

  • Рандомизация хеширования включена по умолчанию.

- https://docs.python.org/3/whatsnew/3.3.html

EDIT: поскольку в комментариях упоминалось, что отношение True/False выше поверхностно удивительно...

Установки, такие как словари, реализуются как хеш-таблицы - поэтому, если есть столкновение, порядок элементов в таблице (и, следовательно, порядок итераций) будет зависеть как от того, какой элемент был добавлен первым (разные в x и y в этом случае) и семя, используемое для хэширования (по-разному между вызовами Python с 3.3). Так как столкновения редки по дизайну, а примеры в этом вопросе - это небольшие множества, проблема возникает не так часто, как можно было предположить изначально.

Подробное объяснение реализации словарей и наборов Python см. в Могущественном словаре.

Ответ 2

Здесь есть две вещи.

  • Установки неупорядочены. set([1, "a", "b", "c", "z", "f"])) == set(["a", "b", "c", "z", "f", 1])

  • При преобразовании набора в кортеж через конструктор tuple он по существу выполняет итерацию по набору и добавляет каждый элемент, возвращаемый итерацией.

Синтаксис конструктора для кортежей

tuple(iterable) -> tuple initialized from iterable items

Вызов tuple(set([1, "a", "b", "c", "z", "f"])) совпадает с вызовом tuple([i for i in set([1, "a", "b", "c", "z", "f"])])

Значения для

[i for i in set([1, "a", "b", "c", "z", "f"])]

и

[i for i in set(["a", "b", "c", "z", "f", 1])]

- те же, что и итерации по одному и тому же набору.

ИЗМЕНИТЬ благодаря @ZeroPiraeus (проверьте его ответ). Это не гарантируется. Значение итерации не всегда будет одинаковым даже для одного и того же набора.

Конструктор кортежа не знает порядка, в котором построено множество.

Ответ 3

Наборы не упорядочены и определяются только их принадлежностью.

Например, set([1, 2]) == set([2, 1])

Кортежи равны, если их члены в каждой позиции равны, но так как коллекции, которые корни были созданы из итерации одинаково (в порядке возрастания), кортежи также оказываются равными.

Ответ 4

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

Когда вы конвертируете эти множества в кортежи, они будут преобразованы в том же порядке, что и тот же набор, поэтому кортежи будут одинаковыми.

Это верно в Python2.7, но начиная с 3.3, когда хеши рандомизированы, вы не сможете этого гарантировать - поскольку два набора, хотя они равны по содержанию, не обязательно должны повторяться в том же порядке.