Есть ли в Python тип битового поля?
Мне нужно компактное представление массива логических значений, есть ли в Python встроенный тип битового поля или мне нужно будет найти альтернативное решение?
10 ответов
Bitarray был лучшим ответом, который я нашел, когда у меня недавно была похожая потребность. Это расширение C (намного более быстрое, чем BitVector, который является чистым Python) и хранит свои данные в реальном битовом поле (таким образом, это в восемь раз более эффективно использует память, чем массивный логический массив, который, кажется, использует байт на элемент.)
Если вы в основном хотите иметь возможность называть свои битовые поля и легко манипулировать ими, например, для работы с флагами, представленными в протоколе связи как отдельные биты, тогда вы можете использовать стандартные функции структуры и объединения ctypes, как описано в разделе Как сделать I Правильно ли объявить структуру ctype + Union в Python? - Переполнение стека
Например, для индивидуальной работы с 4 наименее значимыми битами байта просто назовите их от наименее значимого до самого значимого в LittleEndianStructure. Вы используете объединение для предоставления доступа к тем же данным, что и байты или int, чтобы вы могли перемещать данные в протокол связи или из него. В этом случае это делается через flags.asbyte
поле:
import ctypes
c_uint8 = ctypes.c_uint8
class Flags_bits(ctypes.LittleEndianStructure):
_fields_ = [
("logout", c_uint8, 1),
("userswitch", c_uint8, 1),
("suspend", c_uint8, 1),
("idle", c_uint8, 1),
]
class Flags(ctypes.Union):
_fields_ = [("b", Flags_bits),
("asbyte", c_uint8)]
flags = Flags()
flags.asbyte = 0xc
print(flags.b.idle)
print(flags.b.suspend)
print(flags.b.userswitch)
print(flags.b.logout)
Четыре бита (которые я напечатал здесь, начиная с самого значительного, что кажется более естественным при печати): 1, 1, 0, 0, то есть 0xc в двоичном виде.
Вы должны взглянуть на модуль bitstring, который недавно достиг версии 2.0. Двоичные данные компактно хранятся в виде байтового массива и могут быть легко созданы, изменены и проанализированы.
Вы можете создать BitString
объекты из двоичных, восьмеричных, шестнадцатеричных, целых чисел (с прямым или прямым порядком байтов), строк, байтов, чисел с плавающей запятой, файлов и многого другого.
a = BitString('0xed44')
b = BitString('0b11010010')
c = BitString(int=100, length=14)
d = BitString('uintle:16=55, 0b110, 0o34')
e = BitString(bytes='hello')
f = pack('<2H, bin:3', 5, 17, '001')
Затем вы можете анализировать и изменять их с помощью простых функций или обозначений срезов - не нужно беспокоиться о битовых масках и т. Д.
a.prepend('0b110')
if '0b11' in b:
c.reverse()
g = a.join([b, d, e])
g.replace('0b101', '0x3400ee1')
if g[14]:
del g[14:17]
else:
g[55:58] = 'uint:11=33, int:9=-1'
Существует также концепция битовой позиции, так что вы можете рассматривать ее как файл или поток, если это полезно для вас. Свойства используются, чтобы дать различные интерпретации битовых данных.
w = g.read(10).uint
x, y, z = g.readlist('int:4, int:4, hex:32')
if g.peek(8) == '0x00':
g.pos += 10
Плюс есть поддержка стандартных побитовых двоичных операторов, упаковка, распаковка, порядковый номер и многое другое. Последняя версия предназначена для Python 2.7 и 3.x, и хотя это чистый Python, он достаточно хорошо оптимизирован с точки зрения памяти и скорости.
Представьте каждое из ваших значений как степень двух:
testA = 2**0
testB = 2**1
testC = 2**3
Затем, чтобы установить значение true:
table = table | testB
Чтобы установить значение false:
table = table & (~testC)
Чтобы проверить значение:
bitfield_length = 0xff
if ((table & testB & bitfield_length) != 0):
print "Field B set"
Погрузитесь немного глубже в шестнадцатеричное представление, если это не имеет смысла для вас. Это в основном то, как вы отслеживаете свои логические флаги и во встроенном C-приложении (если у вас ограниченная память).
Я использую двоичные побитовые операторы!, &, |, ^, >> и <<. Они работают очень хорошо и реализуются непосредственно в базовом C, который обычно находится непосредственно на базовом оборудовании.
Пакет BitVector может быть тем, что вам нужно. Он не встроен в мою установку на python, но его легко отследить на сайте python.
https://pypi.python.org/pypi/BitVector для текущей версии.
NumPy имеет модуль интерфейса массива, который вы можете использовать для создания битового поля.
Если ваше битовое поле короткое, вы можете использовать модуль struct. В противном случае я бы порекомендовал какую-то оболочку вокруг модуля массива.
Кроме того, модуль ctypes содержит битовые поля, но я никогда не использовал его сам. Будьте бдительны
Если вы хотите использовать целые числа (или длинные целые числа) для представления в виде массивов bools (или наборов целых чисел), взгляните на http://sourceforge.net/projects/pybitop/files/
Он обеспечивает вставку / извлечение битовых полей в длинные целые числа; поиск наиболее значимого или наименее значимого бита "1"; считая все 1; инвертирования битов; такие вещи, которые возможны в чистом питоне, но гораздо быстрее в C.
Мне нужно было минимальное битовое поле с эффективным использованием памяти без внешних зависимостей, вот оно:
import math
class Bitfield:
def __init__(self, size):
self.bytes = bytearray(math.ceil(size / 8))
def __getitem__(self, idx):
return self.bytes[idx // 8] >> (idx % 8) & 1
def __setitem__(self, idx, value):
mask = 1 << (idx % 8)
if value:
self.bytes[idx // 8] |= mask
else:
self.bytes[idx // 8] &= ~mask
Использование:
# if size is not a multiple of 8, actual size will be the next multiple of 8
bf = Bitfield(1000)
bf[432] # 0
bf[432] = 1
bf[432] # 1
Для большей части последовательных битов есть модуль https://pypi.org/project/range_set/ который API-интерфейс совместим со встроенным в Python set
, Как следует из названия, он хранит биты как пары начало / конец.
Мне пришлось иметь дело с некоторыми контрольными словами / флагами в протоколе связи, и я сосредоточился на том, чтобы редактор предлагал мне имена флагов и переходил к определению флагов с помощью "F3". Приведенный ниже код удовлетворяет этим требованиям (решение с ctypes от @nealmcb, к сожалению, сегодня не поддерживается индексатором PyCharm). Предложения приветствуются:
""" The following bit-manipulation methods are written to take a tuple as input, which is provided by the Bitfield class. The construct
looks weired, however the call to a setBit() looks ok and the editor (PyCharm) suggests all
possible bit names. I did not find a more elegant solution that calls the setBit()-function and needs
only one argument.
Example call:
setBit( STW1.bm01NoOff2() ) """
def setBit(TupleBitField_BitMask):
# word = word | bit_mask
TupleBitField_BitMask[0].word = TupleBitField_BitMask[0].word | TupleBitField_BitMask[1]
def isBit(TupleBitField_BitMask):
# (word & bit_mask) != 0
return (TupleBitField_BitMask[0].word & TupleBitField_BitMask[1]) !=0
def clrBit(TupleBitField_BitMask):
#word = word & (~ BitMask)
TupleBitField_BitMask[0].word = TupleBitField_BitMask[0].word & (~ TupleBitField_BitMask[1])
def toggleBit(TupleBitField_BitMask):
#word = word ^ BitMask
TupleBitField_BitMask[0].word = TupleBitField_BitMask[0].word ^ TupleBitField_BitMask[1]
""" Create a Bitfield type for each control word of the application. (e.g. 16bit length).
Assign a name for each bit in order that the editor (e.g. PyCharm) suggests the names from outside.
The bits are defined as methods that return the corresponding bit mask in order that the bit masks are read-only
and will not be corrupted by chance.
The return of each "bit"-function is a tuple (handle to bitfield, bit_mask) in order that they can be
sent as arguments to the single bit manipulation functions (see above): isBit(), setBit(), clrBit(), toggleBit()
The complete word of the Bitfield is accessed from outside by xxx.word.
Examples:
STW1 = STW1Type(0x1234) # instanciates and inits the bitfield STW1, STW1.word = 0x1234
setBit(STW1.bm00() ) # set the bit with the name bm00(), e.g. bm00 = bitmask 0x0001
print("STW1.word =", hex(STW1.word))
"""
class STW1Type():
# assign names to the bit masks for each bit (these names will be suggested by PyCharm)
# tip: copy the application's manual description here
def __init__(self, word):
# word = initial value, e.g. 0x0000
self.word = word
# define all bits here and copy the description of each bit from the application manual. Then you can jump
# to this explanation with "F3"
# return the handle to the bitfield and the BitMask of the bit.
def bm00NoOff1_MeansON(self):
# 0001 0/1= ON (edge)(pulses can be enabled)
# 0 = OFF1 (braking with ramp-function generator, then pulse suppression & ready for switching on)
return self, 0x0001
def bm01NoOff2(self):
# 0002 1 = No OFF2 (enable is possible)
# 0 = OFF2 (immediate pulse suppression and switching on inhibited)
return self, 0x0002
def bm02NoOff3(self):
# 0004 1 = No OFF3 (enable possible)
# 0 = OFF3 (braking with the OFF3 ramp p1135, then pulse suppression and switching on inhibited)
return self, 0x0004
def bm03EnableOperation(self):
# 0008 1 = Enable operation (pulses can be enabled)
# 0 = Inhibit operation (suppress pulses)
return self, 0x0008
def bm04RampGenEnable(self):
# 0010 1 = Hochlaufgeber freigeben (the ramp-function generator can be enabled)
# 0 = Inhibit ramp-function generator (set the ramp-function generator output to zero)
return self, 0x0010
def b05RampGenContinue(self):
# 0020 1 = Continue ramp-function generator
# 0 = Freeze ramp-function generator (freeze the ramp-function generator output)
return self, 0x0020
def b06RampGenEnable(self):
# 0040 1 = Enable speed setpoint; Drehzahlsollwert freigeben
# 0 = Inhibit setpoint; Drehzahlsollwert sperren (set the ramp-function generator input to zero)
return self, 0x0040
def b07AcknowledgeFaults(self):
# 0080 0/1= 1. Acknowledge faults; 1. Quittieren Störung
return self, 0x0080
def b08Reserved(self):
# 0100 Reserved
return self, 0x0100
def b09Reserved(self):
# 0200 Reserved
return self, 0x0200
def b10ControlByPLC(self):
# 0400 1 = Control by PLC; Führung durch PLC
return self, 0x0400
def b11SetpointInversion(self):
# 0800 1 = Setpoint inversion; Sollwert Invertierung
return self, 0x0800
def b12Reserved(self):
# 1000 Reserved
return self, 0x1000
def b13MotorPotiSPRaise(self):
# 2000 1 = Motorized potentiometer setpoint raise; (Motorpotenziometer Sollwert höher)
return self, 0x2000
def b14MotorPotiSPLower(self):
# 4000 1 = Motorized potentiometer setpoint lower; (Motorpotenziometer Sollwert tiefer)
return self, 0x4000
def b15Reserved(self):
# 8000 Reserved
return self, 0x8000
""" test the constrution and methods """
STW1 = STW1Type(0xffff)
print("STW1.word =", hex(STW1.word))
clrBit(STW1.bm00NoOff1_MeansON())
print("STW1.word =", hex(STW1.word))
STW1.word = 0x1234
print("STW1.word =", hex(STW1.word))
setBit( STW1.bm00NoOff1_MeansON() )
print("STW1.word =", hex(STW1.word))
clrBit( STW1.bm00NoOff1_MeansON() )
print("STW1.word =", hex(STW1.word))
toggleBit(STW1.bm03EnableOperation())
print("STW1.word =", hex(STW1.word))
toggleBit(STW1.bm03EnableOperation())
print("STW1.word =", hex(STW1.word))
print("STW1.bm00ON =", isBit(STW1.bm00NoOff1_MeansON() ) )
print("STW1.bm04 =", isBit(STW1.bm04RampGenEnable() ) )
Он распечатывает:
STW1.word = 0xffff
STW1.word = 0xfffe
STW1.word = 0x1234
STW1.word = 0x1235
STW1.word = 0x1234
STW1.word = 0x123c
STW1.word = 0x1234
STW1.bm00ON = False
STW1.bm04 = True