Является ли python "установленным" стабильным?

Вопрос возник при ответе на другой вопрос SO (там).

Когда я повторяю несколько раз над набором python (не меняя его между вызовами), могу ли я предположить, что он всегда будет возвращать элементы в том же порядке? А если нет, то в чем смысл изменения порядка? Является ли он детерминированным или случайным? Или определена реализация?

И когда я вызываю одну и ту же программу python несколько раз (не случайный, не зависимый от ввода), получаю ли я тот же порядок для наборов?

Основной вопрос заключается в том, зависит ли порядок итерации python от алгоритма, используемого для реализации наборов, или также в контексте выполнения?

Ответы

Ответ 1

Нет никаких официальных гарантий относительно стабильности множеств (или, например, dicts). Однако в реализации CPython, пока ничего не изменяется, элементы будут создаваться в том же порядке. Наборы реализованы как хэш-таблицы с открытой адресацией (с простым зондом), поэтому вставка или удаление элементов может полностью изменить порядок (в частности, когда это вызывает изменение размера, которое реорганизует, как элементы располагаются в памяти.) Вы также можете имеют два идентичных набора, которые тем не менее производят элементы в другом порядке, например:

>>> s1 = {-1, -2}
>>> s2 = {-2, -1}
>>> s1 == s2
True
>>> list(s1), list(s2)
([-1, -2], [-2, -1])

Если вы не уверены, что у вас есть тот же набор, и ничто не коснулось его между двумя итерациями, лучше не полагаться на него, оставаясь тем же. Создание, казалось бы, нерелевантных изменений, скажем, функций, которые вы называете inbetween, может очень трудно найти ошибки.

Ответ 2

И когда я вызываю тот же питон программа неоднократно (не случайная, не зависит от ввода), я получу то же самое упорядочение множеств?

Я могу ответить на эту часть вопроса сейчас после быстрого эксперимента. Используя следующий код:

class Foo(object) :
  def __init__(self,val) :
    self.val = val
  def __repr__(self) :
    return str(self.val)

x = set()
for y in range(500) :
  x.add(Foo(y))
print list(x)[-10:]

Я могу вызвать поведение, о котором я спрашивал в другом вопросе. Если я буду запускать это повторно, то выход будет изменяться, но не на каждом прогоне. Кажется, он "слабо случайен" в том, что он медленно меняется. Это, безусловно, зависит от реализации, поэтому я должен сказать, что я запускаю macports Python2.6 на снежном барсе. Хотя программа будет выдавать один и тот же ответ в течение длительных периодов времени, делая что-то, что влияет на пул энтропии системы (запись на диск в основном работает) когда-нибудь ударит его в другой выход.

Класс Foo - это просто простая обертка int, поскольку эксперименты показывают, что этого не происходит с наборами int. Я думаю, что проблема вызвана отсутствием элементов __eq__ и __hash__ для объекта, хотя я бы очень хотел знать основное объяснение/способы избежать этого. Также полезно было бы каким-то образом воспроизвести/повторить "плохой" прогон. Кто-нибудь знает, какое семя он использует, или как я могу установить это семя?

Ответ 3

set или frozenset по своей сути является неупорядоченной коллекцией. Внутри наборы основаны на хеш-таблице, а порядок ключей зависит как от порядка вставки, так и от hash алгоритма. В CPython (aka standard Python) целые числа меньше размера машинного слова (32 бит или 64 бит) для себя, но текстовые строки, bytes строки и объекты datetime хеш для целых чисел, которые меняются случайным образом; вы можете контролировать это, установив переменную среды PYTHONHASHSEED.

Из документов __hash__:

Заметка

По умолчанию значения __hash__() объектов str, bytes и datetime "соленые" с непредсказуемым случайным значением. Хотя они остаются постоянными в рамках отдельного процесса Python, они не предсказуемы между повторными вызовами Python.

Это предназначено для обеспечения защиты от отказа в обслуживании, вызванного тщательно подобранными входами, которые используют наихудшую производительность при вложении dict, сложность O (n ^ 2). Подробнее см. Http://www.ocert.org/advisories/ocert-2011-003.html.

Изменение значений хеша влияет на порядок итераций dicts, множеств и других сопоставлений. Python никогда не предоставлял гарантии об этом заказе (и обычно он варьируется между 32-битными и 64-битными сборками).

См. Также PYTHONHASHSEED.

Результаты хэш-объектов других классов зависят от деталей метода класса __hash__.

Результатом всего этого является то, что вы можете иметь два набора, содержащих одинаковые строки, но когда вы конвертируете их в списки, они могут сравнивать неравные. Или они не могут. ;) Вот какой код, который демонстрирует это. На некоторых прогонах он будет просто зацикливаться, а не печатать что-либо, но на других запусках он быстро найдет набор, который использует другой порядок для оригинала.

from random import seed, shuffle

seed(42)

data = list('abcdefgh')
a = frozenset(data)
la = list(a)
print(''.join(la), a)

while True:
    shuffle(data)
    lb = list(frozenset(data))
    if lb != la:
        print(''.join(data), ''.join(lb))
        break    

типичный выход

dachbgef frozenset({'d', 'a', 'c', 'h', 'b', 'g', 'e', 'f'})
deghcfab dahcbgef

Ответ 4

Определение множества неупорядочено, уникальные элементы ( "Неупорядоченные коллекции уникальных элементов" ). Вы должны заботиться только об интерфейсе, а не о реализации. Если вы хотите упорядоченное перечисление, вы должны, вероятно, поместить его в список и отсортировать его.

Существует множество различных реализаций Python. Не полагайтесь на недокументированное поведение, так как ваш код может сломаться на разных реализациях Python.

Ответ 5

Определена определенная реализация. спецификация набора говорит только о том, что

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

Почему бы не использовать OrderedDict, чтобы создать свой собственный класс OrderedSet?

Ответ 6

Как указано, это строго деталь реализации.

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

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

(Im знает, что некоторые GC могут изменить порядок памяти в фоновом потоке, но даже это переупорядочение не будет заметным на уровне структур данных, если не возникает ошибка.)