在Python 2和3中分段字节(不是字符串)

时间:2022-06-05 18:09:46

This is turning out to be trickier than I expected. I have a byte string:

这比我预想的要棘手。我有一个字节串:

data = b'abcdefghijklmnopqrstuvwxyz'

I want to read this data in chunks of n bytes. Under Python 2, this is trivial using a minor modification to the grouper recipe from the itertools documentation:

我想以n个字节为单位来读取这些数据。在Python 2中,使用itertools文档中对grouper食谱的一个小修改,这很简单:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return (''.join(x) for x in izip_longest(fillvalue=fillvalue, *args))

With this in place, I can call:

有了这个,我可以打电话:

>>> list(grouper(data, 2))

And get:

并获得:

['ab', 'cd', 'ef', 'gh', 'ij', 'kl', 'mn', 'op', 'qr', 'st', 'uv', 'wx', 'yz']

Under Python 3, this gets trickier. The grouper function as written simply falls over:

在Python 3下,这就变得更复杂了。石斑鱼的功能简单地写下来:

>>> list(grouper(data, 2))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in <genexpr>
TypeError: sequence item 0: expected str instance, int found

And this is because in Python 3, when you iterate over a bytestring (like b'foo'), you get a list of integers, rather than a list of bytes:

这是因为在Python 3中,当你迭代一个bytestring(比如b'foo')时,你会得到一个整数列表,而不是一个字节列表:

>>> list(b'foo')
[102, 111, 111]

The python 3 bytes function will help out here:

python 3字节函数将在这里提供帮助:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return (bytes(x) for x in izip_longest(fillvalue=fillvalue, *args))

Using that, I get what I want:

用它,我得到了我想要的:

>>> list(grouper(data, 2))
[b'ab', b'cd', b'ef', b'gh', b'ij', b'kl', b'mn', b'op', b'qr', b'st', b'uv', b'wx', b'yz']

But (of course!) the bytes function under Python 2 does not behave the same way. It's just an alias for str, so that results in:

但是(当然!)Python 2下的字节函数的行为方式不同。它只是str的别名,所以结果是:

>>> list(grouper(data, 2))
["('a', 'b')", "('c', 'd')", "('e', 'f')", "('g', 'h')", "('i', 'j')", "('k', 'l')", "('m', 'n')", "('o', 'p')", "('q', 'r')", "('s', 't')", "('u', 'v')", "('w', 'x')", "('y', 'z')"]

...which is not at all helpful. I ended up writing the following:

…这一点都没有帮助。我最后写了以下内容:

def to_bytes(s):
    if six.PY3:
        return bytes(s)
    else:
        return ''.encode('utf-8').join(list(s))

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return (to_bytes(x) for x in izip_longest(fillvalue=fillvalue, *args))

This seems to work, but is this really the way to do it?

这似乎可行,但这真的是解决问题的方法吗?

2 个解决方案

#1


3  

Funcy (a library offering various useful utilities, supporting both Python 2 and 3) offers a chunks function that does exactly this:

函数(一个提供各种实用工具的库,支持Python 2和3)提供了一个块函数,它可以做到这一点:

>>> import funcy
>>> data = b'abcdefghijklmnopqrstuvwxyz'
>>> list(funcy.chunks(6, data))
[b'abcdef', b'ghijkl', b'mnopqr', b'stuvwx', b'yz']   # Python 3
['abcdef', 'ghijkl', 'mnopqr', 'stuvwx', 'yz']        # Python 2.7

Alternatively, you could include a simple implementation of this in your program (compatible with both Python 2.7 and 3):

或者,您可以在您的程序中包含一个简单的实现(兼容Python 2.7和3):

def chunked(size, source):
    for i in range(0, len(source), size):
        yield source[i:i+size]

It behaves the same (at least for your data; Funcy's chunks also works with iterators, this doesn't):

它的行为是相同的(至少对于您的数据是如此;函数的块也与迭代器一起工作,这不是):

>>> list(chunked(6, data))
[b'abcdef', b'ghijkl', b'mnopqr', b'stuvwx', b'yz']   # Python 3
['abcdef', 'ghijkl', 'mnopqr', 'stuvwx', 'yz']        # Python 2.7

#2


2  

Using bytes with bytearray would work for both if your string length was divisible by n or you pass a non empty string as the fillvalue:

如果您的字符串长度可以被n整除,或者您将一个非空字符串作为fillvalue,那么使用bytearray的字节可以同时使用:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return ((bytes(bytearray(x))) for x in zip_longest(fillvalue=fillvalue, *args))

py3:

py3:

In [20]: import sys

In [21]: sys.version
Out[21]: '3.4.3 (default, Oct 14 2015, 20:28:29) \n[GCC 4.8.4]'

In [22]: print(list(grouper(data,2)))
[b'ab', b'cd', b'ef', b'gh', b'ij', b'kl', b'mn', b'op', b'qr', b'st', b'uv', b'wx', b'yz']

Py2:

Py2:

In [6]: import sys

In [7]: sys.version
Out[7]: '2.7.6 (default, Jun 22 2015, 17:58:13) \n[GCC 4.8.2]'

In [8]: print(list(grouper(data,2)))
['ab', 'cd', 'ef', 'gh', 'ij', 'kl', 'mn', 'op', 'qr', 'st', 'uv', 'wx', 'yz']

If you passed an empty string you could filter them out:

如果你传递一个空字符串,你可以过滤掉:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return ((bytes(bytearray(filter(None, x)))) for x in zip_longest(fillvalue=fillvalue, *args))

Which will work for any length string.

它适用于任何长度的字符串。

In [29]: print(list(grouper(data,4)))
[b'abcd', b'efgh', b'ijkl', b'mnop', b'qrst', b'uvwx', b'yz']

In [30]: print(list(grouper(data,3)))
[b'abc', b'def', b'ghi', b'jkl', b'mno', b'pqr', b'stu', b'vwx', b'yz']

#1


3  

Funcy (a library offering various useful utilities, supporting both Python 2 and 3) offers a chunks function that does exactly this:

函数(一个提供各种实用工具的库,支持Python 2和3)提供了一个块函数,它可以做到这一点:

>>> import funcy
>>> data = b'abcdefghijklmnopqrstuvwxyz'
>>> list(funcy.chunks(6, data))
[b'abcdef', b'ghijkl', b'mnopqr', b'stuvwx', b'yz']   # Python 3
['abcdef', 'ghijkl', 'mnopqr', 'stuvwx', 'yz']        # Python 2.7

Alternatively, you could include a simple implementation of this in your program (compatible with both Python 2.7 and 3):

或者,您可以在您的程序中包含一个简单的实现(兼容Python 2.7和3):

def chunked(size, source):
    for i in range(0, len(source), size):
        yield source[i:i+size]

It behaves the same (at least for your data; Funcy's chunks also works with iterators, this doesn't):

它的行为是相同的(至少对于您的数据是如此;函数的块也与迭代器一起工作,这不是):

>>> list(chunked(6, data))
[b'abcdef', b'ghijkl', b'mnopqr', b'stuvwx', b'yz']   # Python 3
['abcdef', 'ghijkl', 'mnopqr', 'stuvwx', 'yz']        # Python 2.7

#2


2  

Using bytes with bytearray would work for both if your string length was divisible by n or you pass a non empty string as the fillvalue:

如果您的字符串长度可以被n整除,或者您将一个非空字符串作为fillvalue,那么使用bytearray的字节可以同时使用:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return ((bytes(bytearray(x))) for x in zip_longest(fillvalue=fillvalue, *args))

py3:

py3:

In [20]: import sys

In [21]: sys.version
Out[21]: '3.4.3 (default, Oct 14 2015, 20:28:29) \n[GCC 4.8.4]'

In [22]: print(list(grouper(data,2)))
[b'ab', b'cd', b'ef', b'gh', b'ij', b'kl', b'mn', b'op', b'qr', b'st', b'uv', b'wx', b'yz']

Py2:

Py2:

In [6]: import sys

In [7]: sys.version
Out[7]: '2.7.6 (default, Jun 22 2015, 17:58:13) \n[GCC 4.8.2]'

In [8]: print(list(grouper(data,2)))
['ab', 'cd', 'ef', 'gh', 'ij', 'kl', 'mn', 'op', 'qr', 'st', 'uv', 'wx', 'yz']

If you passed an empty string you could filter them out:

如果你传递一个空字符串,你可以过滤掉:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return ((bytes(bytearray(filter(None, x)))) for x in zip_longest(fillvalue=fillvalue, *args))

Which will work for any length string.

它适用于任何长度的字符串。

In [29]: print(list(grouper(data,4)))
[b'abcd', b'efgh', b'ijkl', b'mnop', b'qrst', b'uvwx', b'yz']

In [30]: print(list(grouper(data,3)))
[b'abc', b'def', b'ghi', b'jkl', b'mno', b'pqr', b'stu', b'vwx', b'yz']