【问题标题】:byte array numpy Python 2 vs Python 3字节数组 numpy Python 2 与 Python 3
【发布时间】:2016-06-20 12:14:43
【问题描述】:

我需要生成这种形式的元组:(string, string) 或 (string, 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 3.5 中从 Python 2 获得所需的结果形式?

此脚本将用于生成 2,000,000 行包,而 numpy 是这一代中最好的,比多处理解决方案更快,但 Python 3.5 中的最终结果不是预期的。

有什么想法吗?代码必须在多个平台(Windows、Linux、Mac)上尽可能快地运行。

【问题讨论】:

    标签: python python-3.x numpy numpy-random


    【解决方案1】:

    为什么

    在 python 2 中,string.ascii_letters 是一个字节字符串开头。当您调用.encode('utf-8') 方法时,python 2 的“魔法”首先使用默认编码对其进行解码,然后根据要求重新编码。在python 2和3中编码的结果都是bytes。

    在 python 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 字节字符串元素组成的数组。它是由 15 个整数组成的 N 个数组。当您稍后调用 .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
    

    【讨论】:

    • 感谢 bytearray 包装,它保持了出色的性能。我在 u.tostring() 上添加了 decode("utf-8"),我仍然有很好的性能(在 Windows 平台上 3.9s 内 2,000,000)。今晚我会在一台旧电脑上查看,它在不到 15 秒内就运行了第一个版本
    【解决方案2】:

    将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 转换为字符串。

    【讨论】:

    • 很好的答案,但是在这台机器上的时间是原始版本的近 3 倍。用 decode("utf-8") 从先前的答案包装字节数组,我花了 11.7 秒而不是 3.9 秒。
    猜你喜欢
    • 2010-12-16
    • 1970-01-01
    • 2011-11-25
    • 1970-01-01
    • 2018-07-22
    • 1970-01-01
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    相关资源
    最近更新 更多