使用Python如何读取一个字节中的位?

44

我有一个文件,其中第一个字节包含编码信息。在Matlab中,我可以使用var = fread(file, 8, 'ubit1')一位一位地读取字节,并通过var(1),var(2)等检索每个位。

在Python中有没有类似的位读取器?

10个回答

35

从文件中读取比特,先读低位。

def bits(f):
    bytes = (ord(b) for b in f.read())
    for b in bytes:
        for i in xrange(8):
            yield (b >> i) & 1

for b in bits(open('binary-file.bin', 'r')):
    print b

已测试过了(顺便提一下,字节采用小端序),ord('\x04')返回4,而使用您的代码应该返回二进制字符串'0000100',但实际上我得到的是'000100000'。 - David
2
它首先给出低位(这是自然的,因为它也首先给出低字节)。但是如果你想要另一种顺序,可以将xrange(8)改为reversed(xrange(8)) - user97370
测试了一下Matlab代码读取文件和你的代码,都能正确从数据文件中返回相同的比特串。将字节转换为比特串后是“00100000”,不确定为什么Daniel G的答案中的转换有误,因为它是有意义的。 - David

33

你能够处理的最小单位是字节。如果你想要按位操作,你需要使用按位运算符

x = 3
#Check if the 1st bit is set:
x&1 != 0
#Returns True

#Check if the 2nd bit is set:
x&2 != 0
#Returns True

#Check if the 3rd bit is set:
x&4 != 0
#Returns False

3
您介意增加更多信息吗?因为问题提出者显然似乎是初学者。 - Bruno Brant
当然,我来自Matlab背景,无法在Python中找到“ubit1”类型代码。 我使用了以下代码: f = open('filename', 'rb') var = f.read(1) 它将var返回为十六进制值字符串'\x04',我该如何获取该字符串的二进制表示形式? - David
谢谢你的回答。我愚蠢地从未想过以那种方式思考它;我仍然太过于固执地以十进制的方式思考事物。不过这个解释非常有道理。 - apraetor

15

13

你不能逐位地读取它们 - 你必须逐字节读取它们。不过,你可以轻松地提取出每个比特:

f = open("myfile", 'rb')
# read one byte
byte = f.read(1)
# convert the byte to an integer representation
byte = ord(byte)
# now convert to string of 1s and 0s
byte = bin(byte)[2:].rjust(8, '0')
# now byte contains a string with 0s and 1s
for bit in byte:
    print bit

尝试过了,对于字节为'\0x04'的示例,上面的代码返回'0b'。 - David
谢谢你的代码,现在它给出了byte=100,这是ord('\0x04')=4的正确二进制表示,但是byte读取应该是'00000100'吗? - David
1
好的,我会很快添加(问题在于它截断了前导零)。 - Daniel G
我意识到一旦我有了二进制值,我可以填充位以获得表示,但是直接读取位似乎很奇怪。 - David
这是Python的一个限制 - 它一次读取整个字节。然后,当您调用bin()时,它会给出该数字在二进制中的最小可能表示(即,使用尽可能少的位数,而不是使用任何标准如8或32位)。如果您想要每个字节的所有八位,您需要在调用bin()之后再次进行填充。 - Daniel G
丹尼尔,非常感谢你的帮助。谢谢你提供快速、直接的解决方案。 - David

12

加入之前的一些答案,我会使用:

[int(i) for i in "{0:08b}".format(byte)]

每读取一个字节的文件,以0x88字节为例的结果如下:

>>> [int(i) for i in "{0:08b}".format(0x88)]
[1, 0, 0, 0, 1, 0, 0, 0]
你可以将其分配给一个变量,并根据您的初始请求进行操作。 "{0.08}"用于保证完整的字节长度

7
读取文件中的一个字节:bytestring = open(filename, 'rb').read(1)。注意:文件是以二进制模式打开的。
要获取位,将bytestring转换为整数:byte = bytestring[0](Python 3)或byte = ord(bytestring[0])(Python 2),然后提取所需的位:(byte >> i) & 1
>>> for i in range(8): (b'a'[0] >> i) & 1
... 
1
0
0
0
0
1
1
0
>>> bin(b'a'[0])
'0b1100001'

2

有两种方法可以返回字节的第i位。 "第一位"可能指高位或低位。

这是一个函数,它接受字符串和索引作为参数,并返回该位置处位的值。如所写,它将低位作为第一位处理。如果您想要先处理高位,请取消注释指定行。

def bit_from_string(string, index):
       i, j = divmod(index, 8)

       # Uncomment this if you want the high-order bit first
       # j = 8 - j

       if ord(string[i]) & (1 << j):
              return 1
       else:
              return 0

索引从0开始。如果您想使索引从1开始,可以在调用divmod函数之前调整索引。

使用示例:

>>> for i in range(8):
>>>       print i, bit_from_string('\x04', i)
0 0
1 0
2 1
3 0
4 0
5 0
6 0
7 0

现在,让我们来看看它的工作原理:
一个字符串由8位字节组成,因此首先使用divmod()将索引分成两个部分:
i:字符串中正确字节的索引
j:该字节中正确位的索引
我们使用ord()函数将string[i]处的字符转换为整数类型。然后,(1 << j)通过左移1位j来计算第j位的值。最后,我们使用按位与运算符来测试该位是否设置。如果是,则返回1,否则返回0。

明白了!感谢您在评论中提供的详细信息。我看了一下位移运算符,但是不知道如何用它来解决这个问题。您的答案帮助我澄清了按位运算符和解决方法。谢谢。 - David

1
假设您有一个名为bloom_filter.bin的文件,其中包含一系列位,并且您想要读取整个文件并将这些位用于数组中。
首先创建数组,在读取后存储位。
from bitarray import bitarray
a=bitarray(size)           #same as the number of bits in the file

打开文件,使用open或者with都可以……这里我将使用open。

f=open('bloom_filter.bin','rb')

现在可以使用以下方式一次性将所有位加载到数组 'a' 中:
f.readinto(a)

“a”现在是一个包含所有位的位数组。

您需要先安装bitarray模块:pip install bitarray - vac
我想指出的一件事是,如果这是一个非常大的文件,那么可能会导致内存限制。这只是需要考虑的一些事情。 - deadbabykitten

0

我认为这是一种更具Python风格的方式:

a = 140
binary = format(a, 'b')

这个代码块的结果是:

'10001100'

我需要获取图像的位平面,这个函数帮助我编写了这个代码块:
def img2bitmap(img: np.ndarray) -> list:
    if img.dtype != np.uint8 or img.ndim > 2:
        raise ValueError("Image is not uint8 or gray")
    bit_mat = [np.zeros(img.shape, dtype=np.uint8) for _ in range(8)]
    for row_number in range(img.shape[0]):
        for column_number in range(img.shape[1]):
            binary = format(img[row_number][column_number], 'b')
            for idx, bit in enumerate("".join(reversed(binary))[:]):
                bit_mat[idx][row_number, column_number] = 2 ** idx if int(bit) == 1 else 0
    return bit_mat

同时通过这个代码块,我能够从提取的位平面生成原始图像

img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
out = img2bitmap(img)
original_image = np.zeros(img.shape, dtype=np.uint8)
for i in range(original_image.shape[0]):
    for j in range(original_image.shape[1]):
        for data in range(8):
            x = np.array([original_image[i, j]], dtype=np.uint8)
            data = np.array([data], dtype=np.uint8)
            flag = np.array([0 if out[data[0]][i, j] == 0 else 1], dtype=np.uint8)
            mask = flag << data[0]
            x[0] = (x[0] & ~mask) | ((flag[0] << data[0]) & mask)
            original_image[i, j] = x[0]

0

我觉得这相当快:

import itertools
data = range(10)
format = "{:0>8b}".format
newdata = (False if n == '0' else True for n in itertools.chain.from_iterable(map(format, data)))
print(newdata) # prints tons of True and False

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接