Недавно я наткнулся на dataType, называемый bytearray
в python. Может ли кто-нибудь предоставить сценарии, в которых требуются bytearrays?
Где используются python bytearrays?
Ответ 1
A bytearray
очень похож на регулярную строку python (str
в python2.x, bytes
в python3), но с важным отличием, тогда как строки неизменяемы, bytearray
являются изменяемыми, немного похожими на a list
одиночных символьных строк.
Это полезно, потому что некоторые приложения используют байтовые последовательности способами, которые плохо работают с неизменяемыми строками. Когда вы делаете много небольших изменений в середине больших фрагментов памяти, например, в движке базы данных или библиотеке изображений, строки работают довольно плохо; так как вам нужно сделать копию целой (возможно большой) строки. bytearray
имеют то преимущество, что позволяют сделать такое изменение, не сделав сначала копию памяти.
Но этот конкретный случай на самом деле скорее является исключением, а не правилом. В большинстве случаев используется сравнение строк или форматирование строк. Для последнего обычно есть копия, поэтому переменный тип не будет иметь никакого преимущества, а для первого, поскольку неизменяемые строки не могут измениться, вы можете вычислить строку hash
строки и сравнить ее как ярлык для сравнения каждого байта в порядке, что почти всегда является большой победой; и поэтому это неизменный тип (str
или bytes
), который является значением по умолчанию; и bytearray
является исключением, когда вам нужны специальные функции.
Ответ 2
Этот ответ был бесстыдным, сорванным с здесь
Пример 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: упаковка двоичной записи
Этот пример - небольшой поворот в последнем примере. Предположим, у вас был большой список Python целых (x, y) координат. Что-то вроде этого:
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
, работает намного быстрее. В простом тесте времени, включающем список из 100000 пунктов, он работает примерно в половине случаев, как версия, которая делает много небольших записей.
Пример 3: Математическая обработка байтовых значений
Тот факт, что bytearrays представляют собой массивы целых чисел, облегчает выполнение определенных видов вычислений. В недавнем проекте встроенных систем я использовал 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))
И вот тот же расчет в Python 2 без bytearray
s:
message = "..." # Message already created
lrc = 0
for b in message:
lrc ^= ord(b)
message += chr(lrc) # Add the LRC byte
Лично мне нравится версия bytearray
. Нет необходимости использовать ord()
, и вы можете просто добавить результат в конце сообщения, а не использовать конкатенацию.
Вот еще один симпатичный пример. Предположим, вы хотели запустить bytearray
через простой XOR-шифр. Для этого нужно сделать один слой:
>>> key = 37
>>> message = bytearray(b"Hello World")
>>> s = bytearray(x ^ key for x in message)
>>> s
bytearray(b'[email protected]\x05rJWIA')
>>> bytearray(x ^ key for x in s)
bytearray(b"Hello World")
>>>
Здесь - ссылка на презентацию
Ответ 3
Если вы посмотрите на документацию для bytearray
, в ней говорится:
Возвращает новый массив байтов. Тип bytearray является изменяемой последовательностью целых чисел в диапазоне 0 <= x < 256.
Напротив, в документации для bytes
говорится:
Возвращает новый "байтовый" объект, который является неизменной последовательностью целых чисел в диапазоне 0 <= x < 256. bytes - неизменяемая версия bytearray - она имеет те же самые методы, что и без мутаций, и те же операции индексирования и нарезки.
Как вы можете видеть, основное различие - это изменчивость. str
методы, которые "меняют" строку, фактически возвращают новую строку с желаемой модификацией. В то время как методы bytearray
, которые изменяют последовательность, фактически изменяют последовательность.
Вы бы предпочли использовать bytearray
, если вы редактируете большой объект (например, буфер пикселя изображения) через его двоичное представление и хотите, чтобы изменения были сделаны на месте для эффективности.
Ответ 4
В Википедии приведен пример XOR-шифрования с использованием Python bytearrays (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()