Установить порядок итерации варьируется от запуска к запуску

Почему порядок итерации набора Python (с одним и тем же содержимым) меняется от запуска к запуску и как я могу сделать его согласованным от запуска к запуску?

Я понимаю, что порядок итерации для набора Python произвольный. Если я помещу «a», «b» и «c» в набор, а затем повторю их, они могут вернуться в любом порядке.

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

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

Лучшее решение, которое я придумал, это:

  1. Скопируйте набор в список.
  2. Применить произвольную сортировку к списку.
  3. Итерация списка вместо набора.

Есть ли более простое решение?

Примечание. Я нашел похожие вопросы на StackOverlow, но ни один из них не решает эту конкретную проблему получения одинаковых результатов от запуска к запуску.


person Adrian McCarthy    schedule 03.10.2010    source источник
comment
Если то, что вы тестируете, заключается в том, что «программа выводит одно и то же оба раза», вариант отсортированного списка — ваш лучший выбор. Если то, что вы тестируете, заключается в том, что «программа создает один и тот же набор оба раза», вам нужно будет выполнить сравнение набора (путем травления вывода обоих прогонов, затем распаковки вывода обоих и сравнения наборов, или что-то морально эквивалентное).   -  person Russell Borogove    schedule 03.10.2010
comment
@Russell: у меня есть модульные тесты, которые проверяют содержимое набора. Но у меня также есть этот тест, который сравнивает выходные данные двух прогонов в качестве проверки работоспособности. Выход частично зависит от порядка элементов в наборе, но только окольным путем.   -  person Adrian McCarthy    schedule 03.10.2010


Ответы (7)


То, что вы хотите, невозможно. Произвольный значит произвольный.

Мое решение будет таким же, как и ваше, вам нужно отсортировать набор, если вы хотите сравнить его с другим.

person Turtle    schedule 03.10.2010
comment
Я предполагаю, что я предположил, что произвольное означает, что это будет зависеть от содержимого, а не от фазы луны. - person Adrian McCarthy; 03.10.2010
comment
Ну, есть произвольное, а есть недетерминированное. Вероятно, есть способ определить порядок в наборе, но я готов поспорить, что это больше проблем, чем пользы. Проверьте упорядоченный набор или аналогичный в python... - person JoshD; 03.10.2010
comment
Даже если бы он был согласован от запуска к запуску, не было бы никакой гарантии, что он будет согласован от машины к машине, от версии python к версии python, cpython по сравнению с jython и т. д. - person Mike Axiak; 03.10.2010
comment
И «то же самое содержимое» также не является гарантией, даже в той же сборке Python на той же машине. Элементы вставляются на основе хеш-значения. Когда несколько элементов имеют одинаковое хеш-значение, они вставляются в разные места в зависимости от порядка их вставки. Удаление элементов может привести к большему различию порядка. Кроме того, есть элементы, хэш-значение которых зависит от их расположения в памяти, что делает его различным между запусками. Вы мало что можете сделать, кроме как использовать sorted() для удобного написания 3 шагов. - person Thomas Wouters; 03.10.2010
comment
Я отметил это как ответ, но мне все еще любопытно, какие изменения от запуска к запуску вызывают такое поведение. Использует ли Python PRNG для генерации констант для хэш-функции? Я говорю об одной и той же программе с одними и теми же входными данными, запущенной дважды подряд на одной и той же машине с одним и тем же интерпретатором Python. - person Adrian McCarthy; 03.10.2010
comment
Не уверен, но я предполагаю, что в какой-то момент все хэшируется по адресу (то есть по id()), и какая-то асинхронная вещь в системе по-разному возмущает диспетчер памяти от запуска к запуску. Я бы не ожидал, что cpython вообще будет использовать PRNG в хешировании. - person Russell Borogove; 03.10.2010
comment
@RussellBorogove Для справки: хэши встроенных функций Python полностью детерминированы, хотя они используют довольно сложные алгоритмы. Например, хэш пустой строки или целое число 0 всегда равно 0. - person pydsigner; 07.10.2015
comment
После некоторых исследований я обнаружил, что bugs.python.org/issue13703 добавил случайное хэширование, но после вопрос и эти комментарии были написаны. Кроме того, по умолчанию он включен только в Python 3.3+; Версии Python 2.6.8+ имеют флаг -R для включения этой функции, как упоминает @AdrianMcCarthy в своем ответе. - person pydsigner; 07.10.2015

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

Вы можете заменить случайное начальное число фиксированным значением, установив переменную среды PYTHONHASHSEED для интерпретатора. Использование одного и того же начального числа от запуска к запуску означает, что итерация набора по-прежнему произвольна, но теперь она детерминирована, что было желаемым свойством.

Рандомизация начальных значений хэша — это мера безопасности, усложняющая злоумышленнику ввод входных данных, которые вызовут патологическое поведение (например, путем создания многочисленных коллизий хэшей). Для модульного тестирования это не проблема, поэтому разумно переопределить хэш-начальное значение при выполнении тестов.

person Adrian McCarthy    schedule 11.09.2015
comment
Добавление случайного хеширования в Python не происходило до 2012 года. - person pydsigner; 07.10.2015
comment
@pydsigner: Это интересно, поскольку это действительно решает проблему, с которой я столкнулся. Я вернулся к этому проекту прошлой осенью, и установка PYTHONHASHSEED сделала вывод моих тестов стабильным от запуска к запуску. - person Adrian McCarthy; 16.03.2016
comment
Действительно интересно.... 2.6.8 и 3.2.3 были версии, в которых это было введено. - person pydsigner; 16.03.2016
comment
@pydsigner У вас также есть ссылка на введение рандомизации (не введение переменной PYTHONHASHSEED)? - person superb rain; 30.08.2020
comment
@superbrain на основе ocert.org/advisories/ocert-2011-003.html Похоже, версии совпадают с добавлением PYTHONHASHSEED; перенесен на версии 2.6.8 и 3.1.5 и доступен везде, кроме версий 2.7.3 и 3.2.3. - person pydsigner; 02.09.2020
comment
@pydsigner Спасибо. Пытался найти его в истории кодов, но безуспешно. Так что я думаю, что действительно была какая-то другая причина, когда был задан вопрос. Ну что ж... - person superb rain; 02.09.2020

Используйте оператор symmetric_difference (^) для двух наборов, чтобы увидеть, есть ли какие-либо различия:

In [1]: s1 = set([5,7,8,2,1,9,0])
In [2]: s2 = set([9,0,5,1,8,2,7])
In [3]: s1
Out[3]: set([0, 1, 2, 5, 7, 8, 9])
In [4]: s2
Out[4]: set([0, 1, 2, 5, 7, 8, 9])
In [5]: s1 ^ s2
Out[5]: set()
person Brian C. Lane    schedule 03.10.2010
comment
Это нормально для прямого сравнения наборов. Однако в своих тестах я ищу простой способ просто сравнить результаты одного прогона с другим, и на этот результат влияет порядок итерации. - person Adrian McCarthy; 11.09.2015

Порядок итерации набора зависит не только от его содержимого, но и от порядка, в котором элементы были вставлены в набор, а также от того, были ли по пути удаления. Таким образом, вы можете создать два разных набора, используя разные вставки и удаления, и в итоге получить один и тот же набор в конце, но с разным порядком итерации.

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

person Ned Batchelder    schedule 03.10.2010
comment
Запуск моей программы два раза подряд с одним и тем же вводом включает одну и ту же последовательность операций вставки, удаления и установки, но порядок итераций все еще меняется. Как будто есть что-то более важное, например, время суток, идентификатор процесса или что-то еще, что меняется от запуска к запуску. - person Adrian McCarthy; 03.10.2010
comment
Томас Воутерс указывает в своем комментарии выше, что некоторые классы используют id() в хеш-функции, что означает, что хэш объекта зависит от его адреса в памяти, и кто знает, что может изменить это. Если вы используете свои собственные классы, вы можете написать свою собственную хеш-функцию, чтобы избавиться от некоторой неопределенности, но вам, вероятно, в любом случае лучше просто отсортировать результаты. - person Ned Batchelder; 03.10.2010

Ваш вопрос превратился в два вопроса: а) как сравнить результат двух прогонов в вашем конкретном случае; Б) каково определение порядка итерации в наборе. Возможно, вам следует различать их и опубликовать B) как новый вопрос, если это уместно. Я отвечу А.

ИМХО, использование отсортированного списка в вашем случае - не очень чистое решение. Вы должны решить, заботитесь ли вы о порядке итерации раз и навсегда, и использовать соответствующую структуру.

Либо 1) вы хотите сравнить два набора, чтобы увидеть, имеют ли они одинаковое содержимое, независимо от порядка. Тогда простой оператор == над множествами кажется подходящим. См. наборы python2, наборы python3.

Или 2) вы хотите проверить, были ли элементы вставлены в том же порядке. Но это кажется разумным только в том случае, если порядок вставки так или иначе важен для пользователей вашей библиотеки, и в этом случае использование типа set, вероятно, было бы неуместным с самого начала. Другими словами, неясно, что именно вы имеете в виду, сравнивая результаты двух прогонов и почему вы хотите это сделать.

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

person Olivier Cailloux    schedule 23.10.2011

Вы можете установить, что ожидаемый результат также будет набором. И проверяет, равны ли эти два набора, используя ==.

person Chuiwen Ma    schedule 01.12.2016

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

person knitti    schedule 03.10.2010
comment
Да, но я создаю наборы, используя несколько операций над наборами (объединения, пересечения и т. д.). Они менее эффективны, чем списки. - person Adrian McCarthy; 03.10.2010