首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >构造:字节和位转换

构造:字节和位转换
EN

Code Review用户
提问于 2016-10-10 21:04:30
回答 1查看 2.2K关注 0票数 0

此代码是构造库的一部分,它在字节和位表示之间进行转换。

代码语言:javascript
复制
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

EN

回答 1

Code Review用户

发布于 2016-10-11 14:36:23

你的代码完全是湿的。我建议您将integer2bitsinteger2bytes合并为一个私有函数。让你把bits2integerbytes2integer合并。这允许您减少所需的测试量。因为现在只需要测试一个函数,而不是两个函数。如果将数字列表而不是字节或字符串传递给它们,也可以简化内部逻辑。

integer2bitsinteger2bytes开始,创建_integer_convert。您应该能够看到前五行几乎是相同的。唯一的区别是你把数字移动了多少。因此,如果我们向函数传递一个变量size来定义数据的大小。您可以将转换更改为1 << width * size

代码语言:javascript
复制
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的大小。这意味着我们可以将函数更改为:

代码语言:javascript
复制
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)比起来更糟糕。

代码语言:javascript
复制
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,而新的测试也证明了这一点。所以这些测试已经比你以前的测试好了。添加所有这些后,我得到了一个函数:

代码语言:javascript
复制
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)

在此之后,我们应该重新定义integer2bitsinteger2bytes,它们将相当潮湿,但是每写两次而不是八次都是两种罪恶中较小的一种。我还会将您的doctest更改为Python 2和Python 3,但是您可以在这里省去它们,因为您只是在测试字符串/字节的连接。因此可以成为:

代码语言:javascript
复制
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))

在此之后,我们应该以同样的方式更改bits2integerbytes2integer。首先,我想指出(1 << (n - 1))*2 == 1 << n。唯一的区别是,当n0时,后者不会出错。所以你可以简化这条线。另一个不同是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,我们可以得到:

代码语言:javascript
复制
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))。现在这两个函数的功能都是一样的。因此,bits2integerbytes2integer的功能可以变成:

代码语言:javascript
复制
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,您的函数在我的代码中看起来很奇怪。

我的代码也不太可能比你的更快,因为我并没有追求速度。这是因为您正在复制代码,在优化您的代码之前,我会删除它。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/143837

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档