此代码是构造库的一部分,它在字节和位表示之间进行转换。
from construct.lib.py3compat import *
def integer2bits(number, width):
r"""
Converts an integer into its binary representation in a b-string. Width is the amount of bits to generate. If width is larger than the actual amount of bits required to represent number in binary, sign-extension is used. If it's smaller, the representation is trimmed to width bits. Each bit is represented as either b'\x00' or b'\x01'. The most significant is first, big-endian. This is reverse to `bits2integer`.
Examples:
>>> integer2bits(19, 8)
b'\x00\x00\x00\x01\x00\x00\x01\x01'
"""
if width < 1:
raise ValueError("width must be positive")
number = int(number)
if number < 0:
number += 1 << width
bits = [b"\x00"] * width
i = width - 1
while number and i >= 0:
bits[i] = int2byte(number & 1)
number >>= 1
i -= 1
return b"".join(bits)
def integer2bytes(number, width):
r"""
Converts a b-string into an integer. This is reverse to `bytes2integer`.
Examples:
>>> integer2bytes(19,4)
'\x00\x00\x00\x13'
"""
if width < 1:
raise ValueError("width must be positive")
number = int(number)
if number < 0:
number += 1 << (width * 8)
acc = [b"\x00"] * width
i = width - 1
while number > 0:
acc[i] = int2byte(number & 255)
number >>= 8
i -= 1
return b"".join(acc)
def onebit2integer(b):
if b in (b"0", b"\x00"):
return 0
if b in (b"1", b"\x01"):
return 1
raise ValueError(r"bit was not recognized as one of: 0 1 \x00 \x01")
def bits2integer(data, signed=False):
r"""
Converts a b-string into an integer. Both b'0' and b'\x00' are considered zero, and both b'1' and b'\x01' are considered one. Set sign to interpret the number as a 2-s complement signed integer. This is reverse to `integer2bits`.
Examples:
>>> bits2integer(b"\x01\x00\x00\x01\x01")
19
>>> bits2integer(b"10011")
19
"""
number = 0
for b in iteratebytes(data):
number = (number << 1) | onebit2integer(b)
if signed and onebit2integer(data[0:1]):
bias = 1 << (len(data) -1)
return number - bias*2
else:
return number
def bytes2integer(data, signed=False):
r"""
Converts a b-string into an integer. This is reverse to `integer2bytes`.
Examples:
>>> bytes2integer(b'\x00\x00\x00\x13')
19
"""
number = 0
for b in iterateints(data):
number = (number << 8) | b
if signed and byte2int(bytes2bits(data[0:1])[0:1]):
bias = 1 << (len(data)*8 -1)
return number - bias*2
else:
return number
def bytes2bits(data):
r"""
Converts between bit and byte representations in b-strings.
Example:
>>> bytes2bits(b'ab')
b"\x00\x01\x01\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x01\x00"
"""
return b"".join(integer2bits(c,8) for c in iterateints(data))
def bits2bytes(data):
r"""
Converts between bit and byte representations in b-strings.
Example:
>>> bits2bytes(b"\x00\x01\x01\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x01\x00")
b'ab'
"""
if len(data) & 7:
raise ValueError("data length must be a multiple of 8")
return b"".join(int2byte(bits2integer(data[i:i+8])) for i in range(0,len(data),8))
def swapbytes(data, linesize=8):
r"""
Performs an endianness swap on a b-string.
Example:
>>> swapbytes(b'00011011', 2)
b'11100100'
>>> swapbytes(b'0000000011111111', 8)
b'1111111100000000'
"""
if len(data) % linesize:
raise ValueError("data length must be multiple of linesize")
if linesize < 1:
raise ValueError("linesize must be a positive number")
return b"".join(data[i:i+linesize] for i in reversed(range(0,len(data),linesize)))其中一些代码引用:https://github.com/construct/construct/blob/master/construct/lib/py3compat.py
相关的测试用例在这里:https://github.com/construct/construct/blob/master/tests/lib/test_binary.py
发布于 2016-10-11 14:36:23
你的代码完全是湿的。我建议您将integer2bits和integer2bytes合并为一个私有函数。让你把bits2integer和bytes2integer合并。这允许您减少所需的测试量。因为现在只需要测试一个函数,而不是两个函数。如果将数字列表而不是字节或字符串传递给它们,也可以简化内部逻辑。
从integer2bits和integer2bytes开始,创建_integer_convert。您应该能够看到前五行几乎是相同的。唯一的区别是你把数字移动了多少。因此,如果我们向函数传递一个变量size来定义数据的大小。您可以将转换更改为1 << width * size。
def _integer_convert(number, width, size):
if width < 1:
raise ValueError("width must be positive")
number = int(number)
if number < 0:
number += 1 << width * size在此之后,您应该注意到在将number传递给int2byte时使用的是位掩码。这个掩码等于(1 << size) - 1。您还需要查看以块表示的数字,其中一个块从i * size开始。并且有mask的大小。这意味着我们可以将函数更改为:
def _integer_convert(number, width, size):
if width < 1:
raise ValueError("width must be positive")
number = int(number)
if number < 0:
number += 1 << width * size
mask = (1 << size) - 1
acc = [b"\x00"] * width
i = width - 1
while number > 0:
acc[i] = number & mask
number >>= size
i -= 1
return acc这是好的,但你可以把它变成一个发电机的理解。这几乎肯定比当前的实现要慢。但如果你对理解很熟悉的话,阅读起来就更容易了。我们将要使用的range将从width-1开始,在0结束。所以我们可以使用range(width-1, -1, -1)。在此之后,我会让块开始,然后对数据执行掩码。这允许通过使用理解来实现更小的代码,但可能比上面的函数慢。和上面的_integer_convert(0, 20, 1)比起来更糟糕。
def _integer_convert(number, width, size):
if width < 1:
raise ValueError("width must be positive")
number = int(number)
if number < 0:
number += 1 << width * size
mask = (1 << size) - 1
chunks = (i * size for i in range(width-1, -1, -1))
return (((number & mask << chunk) >> chunk) for chunk in chunks)为了进一步改进这个函数,我将向其添加doctest。您当前的文档测试是有限的,实际上是错误的。因此,使用元组作为输入,并将迭代器转换为元组作为输出,可以允许更安全和更清晰的测试。我也会测试这个功能的所有方面。一些不同的尺寸,至少尺寸1和8。一些不同的数字,和一对不同的宽度。但你几乎肯定要测试一个负数。你现在不能这么做。实际上,我并没有最初将number += 1 << width更改为number += 1 << width * size,而新的测试也证明了这一点。所以这些测试已经比你以前的测试好了。添加所有这些后,我得到了一个函数:
def _integer_convert(number, width, size):
"""
>>> tuple(_integer_convert(170, 8, 1))
(1, 0, 1, 0, 1, 0, 1, 0)
>>> tuple(_integer_convert(170, 4, 2))
(2, 2, 2, 2)
>>> tuple(_integer_convert(170, 4, 3))
(0, 2, 5, 2)
>>> tuple(_integer_convert(170, 4, 4))
(0, 0, 10, 10)
>>> tuple(_integer_convert(170, 4, 5))
(0, 0, 5, 10)
>>> tuple(_integer_convert(170, 4, 6))
(0, 0, 2, 42)
>>> tuple(_integer_convert(170, 4, 7))
(0, 0, 1, 42)
>>> tuple(_integer_convert(170, 2, 8))
(0, 170)
>>> tuple(_integer_convert(170, 1, 8))
(170,)
>>> _integer_convert(170, 0, 8)
Traceback (most recent call last):
...
ValueError: width must be positive
>>> tuple(_integer_convert(-42, 8, 1))
(1, 1, 0, 1, 0, 1, 1, 0)
>>> tuple(_integer_convert(-170, 1, 8))
(86,)
>>> tuple(_integer_convert(-170, 2, 8))
(255, 86)
>>> tuple(_integer_convert(19, 8, 1))
(0, 0, 0, 1, 0, 0, 1, 1)
>>> tuple(_integer_convert(19, 2, 8))
(0, 19)
"""
if width < 1:
raise ValueError("width must be positive")
number = int(number)
if number < 0:
number += 1 << width * size
mask = (1 << size) - 1
chunks = (i * size for i in range(width-1, -1, -1))
return (((number & mask << chunk) >> chunk) for chunk in chunks)在此之后,我们应该重新定义integer2bits和integer2bytes,它们将相当潮湿,但是每写两次而不是八次都是两种罪恶中较小的一种。我还会将您的doctest更改为Python 2和Python 3,但是您可以在这里省去它们,因为您只是在测试字符串/字节的连接。因此可以成为:
def integer2bits(number, width):
r"""
Converts an integer into its binary representation in a b-string. Width is the amount of bits to generate. If width is larger than the actual amount of bits required to represent number in binary, sign-extension is used. If it's smaller, the representation is trimmed to width bits. Each bit is represented as either b'\x00' or b'\x01'. The most significant is first, big-endian. This is reverse to `bits2integer`.
Examples:
>>> str(integer2bits(19, 8).decode('utf-8'))
'\x00\x00\x00\x01\x00\x00\x01\x01'
"""
return b"".join(int2byte(i) for i in _integer_convert(number, width, 1))
def integer2bytes(number, width):
r"""
Converts a b-string into an integer. This is reverse to `bytes2integer`.
Examples:
>>> str(integer2bytes(19, 4).decode('utf-8'))
'\x00\x00\x00\x13'
"""
return b"".join(int2byte(i) for i in _integer_convert(number, width, 8))在此之后,我们应该以同样的方式更改bits2integer和bytes2integer。首先,我想指出(1 << (n - 1))*2 == 1 << n。唯一的区别是,当n是0时,后者不会出错。所以你可以简化这条线。另一个不同是number = (number << 1) | onebit2integer(b),相反,我会将onebit2integer从这个函数中移除。因此,我会更改for循环以遍历iterateints(data),并将这一行更改为(number << size) | b & mask。这允许任何size,并且可以将特殊的匹配移出函数。最后,如果我们通过iterateints(data)作为数据,我们可以简化检查,如果数字是负数。这是因为我们可以得到第一个数字,或者默认为零,next(iter(data), 0)。并检查是否设置了正确的位,next(iter(data), 0) & 1 << (size - 1)。最后,从_integer_convert添加倒置函数的doctest,以及对signed的一些doctest,我们可以得到:
def _byte_convert(data, signed, size):
r"""
>>> _byte_convert((1, 0, 1, 0, 1, 0, 1, 0), False, 1)
170
>>> _byte_convert((2, 2, 2, 2), False, 2)
170
>>> _byte_convert((0, 2, 5, 2), False, 3)
170
>>> _byte_convert((0, 0, 10, 10), False, 4)
170
>>> _byte_convert((0, 0, 5, 10), False, 5)
170
>>> _byte_convert((0, 0, 2, 42), False, 6)
170
>>> _byte_convert((0, 0, 1, 42), False, 7)
170
>>> _byte_convert((0, 0, 0, 170), False, 8)
170
>>> _byte_convert((170,), False, 8)
170
>>> _byte_convert((1, 1, 0, 1, 0, 1, 1, 0), True, 1)
-42
>>> _byte_convert((1, 1, 0, 1, 0, 1, 1, 0), False, 1)
214
>>> _byte_convert((0, 1, 1, 0, 1, 0, 1, 1, 0), True, 1)
214
>>> _byte_convert((0, 1, 1, 0, 1, 0, 1, 1, 0), False, 1)
214
>>> _byte_convert((86,), True, 8)
86
>>> _byte_convert((255, 86), True, 8)
-170
>>> _byte_convert((1, 0, 1, 0, 1, 0, 1, 0), True, 1)
-86
>>> _byte_convert((2, 2, 2, 2), True, 2)
-86
>>> _byte_convert((170,), True, 8)
-86
"""
mask = (1 << size) - 1
number = 0
for num in data:
number = (number << size) | num & mask
if signed and next(iter(data), 0) & 1 << (size - 1):
return number - (1 << len(data) * size)
else:
return number我说过我们可以将onebit2integer检查从bits2integer的函数中移出,而且我们可以。这只是通过传递[onebit2integer(i) for i in iteratebytes(data)]而不是list(iterateints(data))。现在这两个函数的功能都是一样的。因此,bits2integer和bytes2integer的功能可以变成:
def bits2integer(data, signed=False):
r"""
Converts a b-string into an integer. Both b'0' and b'\x00' are considered zero, and both b'1' and b'\x01' are considered one. Set sign to interpret the number as a 2-s complement signed integer. This is reverse to `integer2bits`.
Examples:
>>> bits2integer(b"\x01\x00\x00\x01\x01")
19
>>> bits2integer(b"10011")
19
"""
return _byte_convert([onebit2integer(i) for i in iteratebytes(data)], signed, 1)
def bytes2integer(data, signed=False):
r"""
Converts a b-string into an integer. This is reverse to `integer2bytes`.
Examples:
>>> bytes2integer(b'\x00\x00\x00\x13')
19
"""
return _byte_convert(list(iterateints(data)), signed, 8)我还没有真正读过其他的函数,但它们看上去还不错。
我想指出的是,Python的默认样式指南PEP8说,我们应该对函数名使用snake_case,而您的则不然。样式指南主要是为了创建一致性,所以您可能想忽略这一点。改变这种情况的一种方法是有两个函数,一个是integer2bits,它调用integer_to_bits。让integer2bits在使用时发出警告,几年后,您可以在某种程度上安全地删除integer2bits函数。这是因为我使用PEP8,您的函数在我的代码中看起来很奇怪。
我的代码也不太可能比你的更快,因为我并没有追求速度。这是因为您正在复制代码,在优化您的代码之前,我会删除它。
https://codereview.stackexchange.com/questions/143837
复制相似问题