我最近在python中遇到了名为bytearray
的dataType。有人可以提供需要字节数组的场景吗?
答案 0 :(得分:50)
bytearray
非常类似于常规python字符串(python2.x中的str
,python3中的bytes
)但有一个重要的区别,而字符串是不可变的< / em>,bytearray
s是可变的,有点像list
个单字符串。
这很有用,因为某些应用程序使用字节序列的方式与不可变字符串表现不佳。当你在大块内存中间进行大量的小改动时,如在数据库引擎或图像库中,字符串的表现非常差;因为你必须复制整个(可能很大的)字符串。 bytearray
的优点是可以在不首先复制内存的情况下进行这种更改。
但是这个特殊情况实际上更像是例外,而不是规则。大多数用途涉及比较字符串或字符串格式。对于后者,无论如何通常都有副本,因此可变类型不会提供任何优势,而对于前者,由于不可变字符串无法更改,您可以计算字符串的hash
并将其作为比较的快捷方式进行比较每个字节按顺序排列,这几乎总是一个巨大的胜利;所以它是默认的不可变类型(str
或bytes
);当您需要它的特殊功能时,bytearray
是个例外。
答案 1 :(得分:48)
这句话从here
中被无耻地扯掉了假设您正在编写一些在套接字连接上接收大消息的网络代码。如果您了解套接字,则知道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.8s,而版本2运行在116.6s(使用+=
级联的版本比较需要230.3s)。因此,虽然执行连接操作仍然更快,但它只会快16%左右。就个人而言,我认为bytearray
版本的清晰编程可能会弥补它。
这个例子在最后一个例子上略有不同。假设你有一个庞大的整数(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点列表的简单时序测试中,它的运行时间大约是制作大量小写的版本的一半。
bytearrays将自身表示为整数数组的事实使得执行某些类型的计算变得更容易。在最近的嵌入式系统项目中,我使用Python通过串行端口与设备通信。作为通信协议的一部分,所有消息都必须使用纵向冗余校验(LRC)字节进行签名。通过对所有字节值进行XOR来计算LRC。 Bytearrays使这种计算变得容易。这是一个版本:
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))
这里是没有bytearray
s的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 :(得分:5)
如果您查看bytearray
的文档,则会说:
返回一个新的字节数组。 bytearray类型是0 <= x <0的范围内的可变整数序列。 256。
相比之下,bytes
的文档说:
返回一个新的“bytes”对象,它是一个不可变的整数序列,范围为0&lt; = x&lt; 256. bytes是bytearray的不可变版本 - 它具有相同的非变异方法和相同的索引和切片行为。
如您所见,主要区别在于可变性。 “更改”字符串的str
方法实际上会返回一个具有所需修改的新字符串。而改变序列的bytearray
方法实际上改变了序列。
如果您正在通过二进制表示编辑大对象(例如图像的像素缓冲区),并且您希望就地进行修改以提高效率,则更倾向于使用bytearray
。
答案 3 :(得分:3)
Wikipedia使用Python的bytearrays(文档字符串缩减)提供了XOR cipher的示例:
#!/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()