【问题标题】:Where are python bytearrays used?python 字节数组在哪里使用?
【发布时间】:2012-02-24 07:54:07
【问题描述】:

我最近在 python 中遇到了名为bytearray 的数据类型。有人可以提供需要字节数组的场景吗?

【问题讨论】:

    标签: python types


    【解决方案1】:

    这个答案被here无耻地抄袭

    示例 1:从片段中组装消息

    假设您正在编写一些网络代码,该代码在套接字连接上接收大消息。如果您了解套接字,就会知道recv() 操作不会等待所有数据到达。相反,它仅返回系统缓冲区中当前可用的内容。因此,要获取所有数据,您可能会编写如下代码:

    # remaining = number of bytes being received (determined already)
    msg = b""
    while remaining > 0:
        chunk = s.recv(remaining)    # Get available data
        msg += chunk                 # Add it to the message
        remaining -= len(chunk)  
    

    此代码的唯一问题是串联 (+=) 的性能很差。因此,Python 2 中常见的性能优化是收集列表中的所有块并在完成后执行连接。像这样:

    # remaining = number of bytes being received (determined already)
    msgparts = []
    while remaining > 0:
        chunk = s.recv(remaining)    # Get available data
        msgparts.append(chunk)       # Add it to list of chunks
        remaining -= len(chunk)  
    msg = b"".join(msgparts)          # Make the final message
    

    现在,这是使用bytearray 的第三种解决方案:

    # remaining = number of bytes being received (determined already)
    msg = bytearray()
    while remaining > 0:
        chunk = s.recv(remaining)    # Get available data
        msg.extend(chunk)            # Add to message
        remaining -= len(chunk)  
    

    注意bytearray 版本非常干净。您不会在列表中收集零件,也不会在最后执行那种神秘的连接。不错。

    当然,最大的问题是它是否执行。为了测试这一点,我首先制作了一个这样的小字节片段列表:

    chunks = [b"x"*16]*512
    

    然后我使用timeit模块比较了以下两个代码片段:

    # Version 1
    msgparts = []
    for chunk in chunks:
        msgparts.append(chunk)
    msg = b"".join(msgparts)
    
    #Version 2
    msg = bytearray()
    for chunk in chunks:
        msg.extend(chunk)
    

    经过测试,代码版本 1 的运行时间为 99.8 秒,而版本 2 的运行时间为 116.6 秒(相比之下,使用+= 连接的版本需要 230.3 秒)。因此,虽然执行连接操作仍然更快,但它只快了大约 16%。就个人而言,我认为bytearray 版本的更简洁的编程可能会弥补这一点。

    示例2:二进制记录打包

    此示例与上一个示例略有不同。假设您有一个包含整数 (x,y) 坐标的大型 Python 列表。像这样的东西: points = [(1,2),(3,4),(9,10),(23,14),(50,90),...] 现在,假设您需要将该数据作为二进制编码文件写出,该文件由一个 32 位整数长度后跟打包成一对 32 位整数的每个点组成。一种方法是像这样使用 struct 模块:

    import struct
    f = open("points.bin","wb")
    f.write(struct.pack("I",len(points)))
    for x,y in points:
        f.write(struct.pack("II",x,y))
    f.close()
    

    此代码的唯一问题是它执行了大量小的write() 操作。另一种方法是将所有内容打包到bytearray 中,最后只执行一次写入。例如:

    import struct
    f = open("points.bin","wb")
    msg = bytearray()
    msg.extend(struct.pack("I",len(points))
    for x,y in points:
        msg.extend(struct.pack("II",x,y))
    f.write(msg)
    f.close()
    

    果然,使用bytearray 的版本运行速度要快得多。在涉及 100000 点列表的简单时序测试中,它的运行时间大约是进行大量小写入的版本的一半。

    示例 3:字节值的数学处理

    字节数组将自身呈现为整数数组这一事实使得执行某些类型的计算变得更加容易。在最近的一个嵌入式系统项目中,我使用 Python 通过串行端口与设备进行通信。作为通信协议的一部分,所有消息都必须使用纵向冗余校验 (LRC) 字节进行签名。 LRC 是通过对所有字节值进行 XOR 来计算的。 字节数组使这样的计算变得容易。这是一个版本:

    message = bytearray(...)     # Message already created
    lrc = 0
    for b in message:
        lrc ^= b
    message.append(lrc)          # Add to the end of the message
    

    以下版本可提高您的工作安全性: message.append(functools.reduce(lambda x,y:x^y,message)) 这是没有bytearrays 的 Python 2 中的相同计算:

    message = "..."       # Message already created
    lrc = 0
    for b in message:
        lrc ^= ord(b)
    message += chr(lrc)        # Add the LRC byte
    

    就个人而言,我喜欢bytearray 版本。无需使用ord(),您只需将结果附加到消息末尾即可,而不是使用串联。

    这是另一个可爱的例子。假设您想通过一个简单的 XOR 密码运行 bytearray。这是一个单行代码:

    >>> key = 37
    >>> message = bytearray(b"Hello World")
    >>> s = bytearray(x ^ key for x in message)
    >>> s
    bytearray(b'm@IIJ\x05rJWIA')
    >>> bytearray(x ^ key for x in s)
    bytearray(b"Hello World")
    >>> 
    

    Here 是演示文稿的链接

    【讨论】:

    【解决方案2】:

    bytearray 与常规 python 字符串(python2.x 中的str,python3 中的bytes)非常相似,但有一个重要区别,而字符串是不可变,@987654324 @s 是可变的,有点像单个字符串的list

    这很有用,因为某些应用程序使用字节序列的方式对不可变字符串表现不佳。当您在大块内存中进行大量小更改时,例如在数据库引擎或图像库中,字符串的性能很差;因为您必须复制整个(可能很大)字符串。 bytearrays 的优势在于无需先复制内存即可进行此类更改。

    但这种特殊情况实际上更多的是例外,而不是规则。大多数用途涉及比较字符串或字符串格式。对于后者,无论如何通常都有一个副本,因此可变类型不会提供任何优势,而对于前者,由于不可变字符串无法更改,您可以计算字符串的 hash 并将其作为比较每个字节的快捷方式进行比较按顺序,这几乎总是一个巨大的胜利;所以它是默认的不可变类型(strbytes); bytearray 是您需要特殊功能时的例外。

    【讨论】:

    • 此外,字节数组允许将其元素操作为 0-256 范围内的数字或单字符字符串,就像在 C 和其他派生语言中所做的那样。在这方面它非常灵活。
    • 请注意,在 python 2.x 中,您可以使用整数设置字节数组的索引(例如 myarray[x] = 116) or a single string character (e.g. myarray[x] = 't'`)。在 python 3.x 中,only 允许使用整数;尝试将字符串值设置为字节数组的索引将导致 TypeError。
    • +Jordan Reiter +jsbueno 在我看来你的 cmets 是矛盾的;您是否允许将字节数组的元素作为单字符字符串进行操作? (在 Python 3.x 中,因为我猜 jsbueno 的评论适用于所有 Python 版本?)
    • @HelloGoodbye:我之前没有注意到关于 py3 的事情。如您所见,使用 bytearray[n] 需要 int 类型的项目。您仍然可以使用切片以“字符串”的形式操作字节数组,尽管foo[3] = 'x' 不起作用,foo[3:4] = b'x' 工作正常。您必须指定替换工作的开始和结束位置,并且必须使用 bytes 值,不允许使用 python3 str
    【解决方案3】:

    如果您查看bytearray 的文档,它会说:

    返回一个新的字节数组。 bytearray 类型是 0

    相比之下,bytes 的文档说:

    返回一个新的“bytes”对象,它是一个 0

    如您所见,主要区别在于可变性。 str“更改”字符串的方法实际上返回一个带有所需修改的新字符串。而bytearray 改变序列的方法实际上改变了序列

    如果您正在通过二进制表示编辑大型对象(例如图像的像素缓冲区)并且您希望就地完成修改以提高效率,则您更喜欢使用 bytearray

    【讨论】:

    • 我认为这不一定是正确的回复:bytearray 方法,其中许多被描述为“返回对象/序列的副本”。 (从 3.6 开始)。我不确定,但我认为主要的好处是能够访问潜在的大缓冲区而无需任何中间复制(进入 python 对象,可能主要是字符串?);所以对于可变性,我认为好处来自索引/切片而不是实际改变'bytearray'等的方法。
    【解决方案4】:

    维基百科提供了一个XOR cipher 使用 Python 字节数组的示例(减少了文档字符串):

    #!/usr/bin/python2.7
    
    from os import urandom
    
    def vernam_genkey(length):
        """Generating a key"""
        return bytearray(urandom(length))
    
    def vernam_encrypt(plaintext, key):
        """Encrypting the message."""
        return bytearray([ord(plaintext[i]) ^ key[i] for i in xrange(len(plaintext))])
    
    def vernam_decrypt(ciphertext, key):
        """Decrypting the message"""
        return bytearray([ciphertext[i] ^ key[i] for i in xrange(len(ciphertext))])
    
    def main():
        myMessage = """This is a topsecret message..."""
        print 'message:',myMessage
        key = vernam_genkey(len(myMessage))
        print 'key:', str(key)
        cipherText = vernam_encrypt(myMessage, key)
        print 'cipherText:', str(cipherText)
        print 'decrypted:', vernam_decrypt(cipherText,key)
    
        if vernam_decrypt(vernam_encrypt(myMessage, key),key)==myMessage:
            print ('Unit Test Passed')
        else:
            print('Unit Test Failed - Check Your Python Distribution')
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

    • 你可以写:vernam_encrypt = vernam_decrypt = lambda data, key: bytearray(a^b for a, b in zip(*map(bytearray, [data, key])))
    • print(binascii.hexlify(key).decode()) 将密钥打印为十六进制字符串。要将其转换回来:key = binascii.unhexlify(hex_string).
    猜你喜欢
    • 2011-06-28
    • 1970-01-01
    • 2017-12-21
    • 1970-01-01
    • 2014-12-28
    • 1970-01-01
    • 1970-01-01
    • 2020-06-11
    • 1970-01-01
    相关资源
    最近更新 更多