Байтовый массив numpy Python 2 против Python 3
Мне нужно создать кортежи этой формы: (строка, строка) или (строка, int).
У меня есть следующий код, который, кажется, отлично работает в Python 2, но не возвращает желаемый результат в Python 3 (проверено на Python 3.5):
import string
import numpy as np
global_tab = []
global_nb_loop = 0
def numpy_test(N=2000000):
global global_tab
global global_nb_loop
global_nb_loop = N
print("Generate %d lines" % global_nb_loop)
global_tab = [(u.tostring(),str(v)) for u,v in zip( np.random.choice(list(string.ascii_letters.encode("utf-8")), (N, 15)), np.random.randint(0, 100, N) )]
print("%d lines generated" % len(global_tab))
numpy_test(10)
for x in range(10):
print("%d : %s" % (x, global_tab[x]))
В Python 2 результаты таковы:
Generate 10 lines
10 lines generated
0 : ('zvtMIBpQZhjpyqt', '63')
1 : ('mVMkbqBHetqEJdc', '70')
2 : ('uWAwOYIBwzyDdhR', '54')
3 : ('WZvXdFYewrOIYfp', '90')
4 : ('uzszDaTwajsADag', '37')
5 : ('HmBSpSBbQeOixII', '88')
6 : ('VACSDjDtQqqjPWh', '84')
7 : ('XiZJbYQkgpgohMJ', '93')
8 : ('JiFSbeUBYtqhXQk', '93')
9 : ('xLuBXBGYPTogDwo', '41')
В Python 3.5 результаты выглядят так:
Generate 10 lines
10 lines generated
0 : (b'z\x00\x00\x00v\x00\x00\x00t\x00\x00\x00M\x00\x00\x00I\x00\x00\x00B\x00\x00\x00p\x00\x00\x00Q\x00\x00\x00Z\x00\x00\x00h\x00\x00\x00j\x00\x00\x00p\x00\x00\x00y\x00\x00\x00q\x00\x00\x00t\x00\x00\x00', '63')
1 : (b'm\x00\x00\x00V\x00\x00\x00M\x00\x00\x00k\x00\x00\x00b\x00\x00\x00q\x00\x00\x00B\x00\x00\x00H\x00\x00\x00e\x00\x00\x00t\x00\x00\x00q\x00\x00\x00E\x00\x00\x00J\x00\x00\x00d\x00\x00\x00c\x00\x00\x00', '70')
2 : (b'u\x00\x00\x00W\x00\x00\x00A\x00\x00\x00w\x00\x00\x00O\x00\x00\x00Y\x00\x00\x00I\x00\x00\x00B\x00\x00\x00w\x00\x00\x00z\x00\x00\x00y\x00\x00\x00D\x00\x00\x00d\x00\x00\x00h\x00\x00\x00R\x00\x00\x00', '54')
3 : (b'W\x00\x00\x00Z\x00\x00\x00v\x00\x00\x00X\x00\x00\x00d\x00\x00\x00F\x00\x00\x00Y\x00\x00\x00e\x00\x00\x00w\x00\x00\x00r\x00\x00\x00O\x00\x00\x00I\x00\x00\x00Y\x00\x00\x00f\x00\x00\x00p\x00\x00\x00', '90')
4 : (b'u\x00\x00\x00z\x00\x00\x00s\x00\x00\x00z\x00\x00\x00D\x00\x00\x00a\x00\x00\x00T\x00\x00\x00w\x00\x00\x00a\x00\x00\x00j\x00\x00\x00s\x00\x00\x00A\x00\x00\x00D\x00\x00\x00a\x00\x00\x00g\x00\x00\x00', '37')
5 : (b'H\x00\x00\x00m\x00\x00\x00B\x00\x00\x00S\x00\x00\x00p\x00\x00\x00S\x00\x00\x00B\x00\x00\x00b\x00\x00\x00Q\x00\x00\x00e\x00\x00\x00O\x00\x00\x00i\x00\x00\x00x\x00\x00\x00I\x00\x00\x00I\x00\x00\x00', '88')
6 : (b'V\x00\x00\x00A\x00\x00\x00C\x00\x00\x00S\x00\x00\x00D\x00\x00\x00j\x00\x00\x00D\x00\x00\x00t\x00\x00\x00Q\x00\x00\x00q\x00\x00\x00q\x00\x00\x00j\x00\x00\x00P\x00\x00\x00W\x00\x00\x00h\x00\x00\x00', '84')
7 : (b'X\x00\x00\x00i\x00\x00\x00Z\x00\x00\x00J\x00\x00\x00b\x00\x00\x00Y\x00\x00\x00Q\x00\x00\x00k\x00\x00\x00g\x00\x00\x00p\x00\x00\x00g\x00\x00\x00o\x00\x00\x00h\x00\x00\x00M\x00\x00\x00J\x00\x00\x00', '93')
8 : (b'J\x00\x00\x00i\x00\x00\x00F\x00\x00\x00S\x00\x00\x00b\x00\x00\x00e\x00\x00\x00U\x00\x00\x00B\x00\x00\x00Y\x00\x00\x00t\x00\x00\x00q\x00\x00\x00h\x00\x00\x00X\x00\x00\x00Q\x00\x00\x00k\x00\x00\x00', '93')
9 : (b'x\x00\x00\x00L\x00\x00\x00u\x00\x00\x00B\x00\x00\x00X\x00\x00\x00B\x00\x00\x00G\x00\x00\x00Y\x00\x00\x00P\x00\x00\x00T\x00\x00\x00o\x00\x00\x00g\x00\x00\x00D\x00\x00\x00w\x00\x00\x00o\x00\x00\x00', '41')
Конечно, если я удалю все \x00, у меня будет желаемый результат.
Результаты связаны с Python 3.5, так как Windows или Linux Python 3.5 возвращают байтовый массив того же типа.
Как я могу получить желаемый результат формы из Python 2 в Python 3.5?
Этот сценарий будет использоваться для генерации пакетов в 2 000 000 строк, и numpy был лучшим для этого поколения, он работал быстрее, чем многопроцессорное решение, но конечный результат в Python 3.5 не тот, который ожидался.
Есть идеи? Код должен работать максимально быстро на нескольких платформах (Windows, Linux, Mac).
2 ответа
Зачем
В питоне 2 string.ascii_letters
это строка байтов для начала. "Волшебство" Python 2 сначала декодирует его с кодировкой по умолчанию при вызове метода .encode('utf-8')
и затем перекодирует в соответствии с запросом. Результат кодирования bytes
в обоих питонах 2 и 3.
В питоне 3 строка байтов ведет себя по-разному, когда повторяется: она возвращает целые числа, а не строки байтов длины 1:
In [52]: list(string.ascii_letters.encode('utf-8'))
Out[52]:
[97,
98,
99,
...
Таким образом, в Python 3 результат
np.random.choice(list(string.ascii_letters.encode('utf-8')), (N, 15))
не является N массивом из 15 1-байтовых строковых элементов. Это N массивов из 15 целых чисел. Как ты потом позвонишь .tostring()
чтобы получить необработанные байты массива, вы получите 4 или 8 байтовых целых чисел. В вашем примере вы, кажется, получаете 4, на этой машине их 8.
Возможные исправления
Один из вариантов - добавить актерский состав:
In [63]: [(u.tostring(),str(v)) for u, v in zip(
np.random.choice(list(string.ascii_letters.encode("utf-8")),
(N, 15)).astype('|S1'), # Cast to array-protocol type string
np.random.randint(0, 100, N))]
Out[63]:
[(b'811881611111171', '82'),
(b'816878668111171', '46'),
(b'811118881668718', '53'),
(b'971861817181818', '49'),
(b'118618991678978', '81'),
...
Другой вариант - полностью пропустить кодировку, доверять родным типам строк, если это возможно (если вам действительно не нужны байтовые строки), и использовать str.join()
:
In [74]: [(''.join(u), str(v)) for u, v in zip(
np.random.choice(list(string.ascii_letters),
(N, 15)),
np.random.randint(0, 100, N))]
Out[74]: [('IJTlleYqZXmSJaW', '32')]
Третье было бы обернуть bytearray()
вместо list()
:
In [95]: [(u.tostring(), str(v)) for u, v in zip(
np.random.choice(bytearray(string.ascii_letters.encode('utf-8')),
(N, 15)),
np.random.randint(0, 100, N))]
Out[95]: [(b'MPvbDEQIdAVBQVz', '83')]
Некоторые сроки
Вот как они выступили на этой машине в Python 3 с N = 2000000
:
Оригинал без (обязательного) актерского состава:
In [69]: %timeit [(u.tostring(), str(v)) for u, v in zip( np.random.choice(list(string.ascii_letters.encode('utf-8')), (N, 15)), np.random.randint(0, 100, N))]
1 loops, best of 3: 4.62 s per loop
С актерами:
In [70]: %timeit [(u.tostring(), str(v)) for u, v in zip( np.random.choice(list(string.ascii_letters.encode('utf-8')), (N, 15)).astype('|S1'), np.random.randint(0, 100, N))]
1 loops, best of 3: 7.07 s per loop
Используя нативный тип строки и соединение:
In [71]: %timeit [(''.join(u), str(v)) for u, v in zip( np.random.choice(list(string.ascii_letters), (N, 15)), np.random.randint(0, 100, N))]
1 loops, best of 3: 12.1 s per loop
Упаковка с bytearray()
:
In [93]: %timeit [(u.tostring(), str(v)) for u, v in zip( np.random.choice(bytearray(string.ascii_letters.encode('utf-8')), (N, 15)), np.random.randint(0, 100, N))]
1 loops, best of 3: 4.56 s per loop
Замещать global_tab
с:
global_tab = [(''.join(u), str(v)) for u, v in zip(np.random.choice(list(string.ascii_letters), (N, 15)), np.random.randint(0, 100, N) )]
ascii_letters
имеют строковый тип, поэтому вам может не понадобиться вызывать encode('utf-8')
:
>>> from string import ascii_letters
>>> ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> type(ascii_letters)
<class 'str'>
Тогда вы будете использовать .join
преобразовать полученный массив u
в строку.