2016-08-14 9 views
5

Я пишу программу, совместимую как с Python 2.7, так и с 3.5. Некоторые его части полагаются на случайный процесс. В моих модульных тестах используется произвольное семя, что приводит к одинаковым результатам во всех исполнениях и языках ... кроме кода с использованием random.shuffle.Разница между Python 2 и 3 для тасования с заданным семенем

Пример в Python 2.7:

In[]: import random 
     random.seed(42) 
     print(random.random()) 
     l = list(range(20)) 
     random.shuffle(l) 
     print(l) 
Out[]: 0.639426798458 
     [6, 8, 9, 15, 7, 3, 17, 14, 11, 16, 2, 19, 18, 1, 13, 10, 12, 4, 5, 0] 

тот же вход в Python 3.5:

In []: import random 
     random.seed(42) 
     print(random.random()) 
     l = list(range(20)) 
     random.shuffle(l) 
     print(l) 
Out[]: 0.6394267984578837 
     [3, 5, 2, 15, 9, 12, 16, 19, 6, 13, 18, 14, 10, 1, 11, 4, 17, 7, 8, 0] 

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

Как я могу написать тот же тестовый код для двух версий Python?

+0

Кроме того, быстрый взгляд на [реализация Py2] (https://svn.python.org/projects/python/tags/r27/Lib/random.py) и [реализация Py3] (https: //svn.python .org/projects/python/tags/r32/Lib/random.py) показывает, что существует несколько реализаций тасования (хотя они могут быть эквивалентными - я бы ожидал, что для этого должны быть unittests, когда была введена модификация). –

+0

Попробуйте '' 'random.seed (42, version = 1)' '' (или, может быть, version = 2, но установите этот аргумент), как описано [здесь] (https://docs.python.org/3/library/ random.html # random.seed). – sascha

+0

'[random.random() for _ in range (20)]' возвращает одинаковые результаты в обеих версиях - похоже, это изменение реализации в 'random.shuffle' ... –

ответ

14

В Python 3.2 случайный модуль был реорганизован немного, чтобы сделать выход однородным по архитектуре (с учетом того же самого семени), см. issue #7889. Метод shuffle() был переключен на использование Random._randbelow().

Однако метод _randbelow() был также регулируется, так что просто скопировать версию shuffle() 3.5 не достаточно, чтобы исправить это.

Тем не менее, если вы проходите в своей собственной random() функции, реализация в Python 3.5 отличается от версии 2.7, и, таким образом, позволяет обойти это ограничение:

random.shuffle(l, random.random) 

Примечание Однако, чем сейчас вы подвержены старым 32-битным и 64-разрядным различиям архитектуры, которые пытались решить.

Игнорирование несколько оптимизаций и особые случаи, если вы включите _randbelow() версию 3.5 можно портированном как:

import random 
import sys 

if sys.version_info >= (3, 2): 
    newshuffle = random.shuffle 
else: 
    try: 
     xrange 
    except NameError: 
     xrange = range 

    def newshuffle(x): 
     def _randbelow(n): 
      "Return a random int in the range [0,n). Raises ValueError if n==0." 
      getrandbits = random.getrandbits 
      k = n.bit_length() # don't use (n-1) here because n can be 1 
      r = getrandbits(k)   # 0 <= r < 2**k 
      while r >= n: 
       r = getrandbits(k) 
      return r 

     for i in xrange(len(x) - 1, 0, -1): 
      # pick an element in x[:i+1] with which to exchange x[i] 
      j = _randbelow(i+1) 
      x[i], x[j] = x[j], x[i] 

, который дает тот же результат на 2,7, как 3,5:

>>> random.seed(42) 
>>> print(random.random()) 
0.639426798458 
>>> l = list(range(20)) 
>>> newshuffle(l) 
>>> print(l) 
[3, 5, 2, 15, 9, 12, 16, 19, 6, 13, 18, 14, 10, 1, 11, 4, 17, 7, 8, 0] 
+0

@Marijn Pieters Это просто отлично, и в качестве бонуса я могу использовать те же тесты, которые я уже написал для Python 2. Я бы прочитал этот вопрос. – Aristide

+0

На самом деле, кажется, что мои тесты по-прежнему не работают в обеих версиях. В частности, версия Py3 иногда проходит, а иногда и терпит неудачу, что доказывает, что моего семени недостаточно, чтобы сделать его детерминированным. Я предполагаю, что другие «случайные» функции, которые я использую (а именно «randrange» и «выбор»), имеют такую ​​же проблему. Очень странно. – Aristide

+0

Да, 'choice' и' randrange' также были реорганизованы, см. [Этот патч] (https://hg.python.org/cpython/diff/c2f8418a0e14/Lib/random.py). –

1

Конкретизируя Martijn Pieters отличный ответ и комментарии, и на этом discussion я наконец нашел обходное решение, которое, возможно, не отвечает на мой вопрос, но в то же время не требует глубоких изменений. Подводя итог:

  • random.seed фактически делает каждую random функции детерминированным, но не обязательно производит такой же вывод между версиями;
  • установки PYTHONHASHSEED в 0 отключает хэш рандомизации для словарей и наборов, которые по умолчанию вводит коэффициент индетерминизма в Python 3.

Итак, в сценарии Баш, который запускает 3 тестов Python, я добавил :

export PYTHONHASHSEED=0 

Затем я временно изменил мои тестовые функции, чтобы отладочный заставить мой путь к целому семени, которое бы воспроизводит в Python 3 ожидаемые результаты в Python 2.И, наконец, я вернулся свои изменения и заменить строки:

seed(42) 

что-то вроде этого:

seed(42 if sys.version_info.major == 2 else 299) 

Ничего хвастать, но, как говорится, иногда практичности бьет чистоту;)

Это быстрое обходное решение может быть полезно для тех, кто хочет протестировать один и тот же стохастический код в разных версиях Python!