python bytearrays在哪里使用?
我最近遇到了python中称为bytearray
的dataType。 有人可以提供需要bytearrays的场景吗?
bytearray
与普通的pythonstring非常相似(python2.x中的str
,python3中的bytes
),但是有一个重要的区别,而string是不可变的 , bytearray
是可变的,有点像单个string的list
。
这是有用的,因为一些应用程序使用不可变string执行较差的字节序列。 如果在数据库引擎或图像库中,在大块内存中进行大量小改动,则string性能会很差; 因为你必须复制整个(可能是大的)string。 bytearray
具有可以在不复制内存的情况下进行这种更改的优点。
但是这个特例实际上更多的是例外,而不是规则。 大多数用途涉及比较string或string格式。 对于后者,无论如何通常都有一个副本,所以可变types将不会提供任何优势,而对于前者,由于不可变的string不能更改,因此可以计算string的hash
并将其作为比较每个字节的快捷方式,这几乎总是一个大胜利; 所以它是默认的不可变types( str
或者bytes
) 当你需要它的特殊function时, bytearray
是个例外。
这个答案已经被无耻地从这里撕下来了
示例1:从碎片组装消息
假设你正在编写一些在套接字连接上接收大消息的networking代码。 如果您知道套接字,则知道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
的第三个解决scheme:
# 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
版本是干净的。 您不会收集列表中的部分,也不会在最后执行那种神秘的连接。 尼斯。
当然,最大的问题是它是否performance出色。 为了testing这个,我首先制作了一个小字节的列表,如下所示:
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)
testing版本1的代码运行在99.8s,而版本2运行在116.6s(使用+=
连接的版本比较需要230.3s)。 所以在执行连接操作的时候速度还是比较快,只有16%左右。 我个人认为, bytearray
版本的更清晰的编程可能会弥补。
示例2:二进制logging打包
这个例子是最后一个例子的一个轻微的转折。 假设你有一个整数(x,y)坐标的大Python列表。 就像这样: points = [(1,2),(3,4),(9,10),(23,14),(50,90),...]
现在,假设你需要写这些数据作为一个二进制编码的文件,包括一个32位整数长度,然后每个点打包成一对32位整数。 一种方法是使用这样的结构模块:
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
的版本运行得更快。 在一个简单的时间testing中,涉及一个100000点的列表,它的运行时间大约是作出大量小写操作的版本的一半。
示例3:字节值的math处理
字节数组自身呈现为整数数组的事实使得执行某些types的计算更容易。 在最近的embedded式系统项目中,我使用Python通过串口与设备进行通信。 作为通信协议的一部分,所有消息都必须使用纵向冗余校验(LRC)字节进行签名。 通过对所有字节值进行XOR来计算LRC。 字节数据使得这样的计算变得容易。 这里有一个版本:
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))
这里是Python 2中没有bytearray
的同样的计算:
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") >>>
这是一个演示文稿的链接
如果你看看bytearray
的文档,它说:
返回一个新的字节数组。 bytearraytypes是一个可变的整数序列,范围为0 <= x <256。
相反, bytes
的文档说:
返回一个新的“bytes”对象,它是一个在0 <= x <256范围内的不可变整数序列。bytes是bytearray的不变版本 – 它具有相同的非变异方法和相同的索引和切片行为。
正如你所看到的,主要的区别是可变性。 str
方法“改变”string实际上返回一个新的string与所需的修改。 改变序列的bytearray
方法实际上改变了序列 。
如果您通过二进制表示编辑大型对象(例如图像的像素缓冲区),并且希望在原地进行修改以提高效率,则宁愿使用bytearray
。
Wikipedia提供了一个使用Python的bytearrays进行XOR密码的例子(docstrings reduced):
#!/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()