2016-02-23 27 views
9

To się okazało być trudniejsze niż się spodziewałem. Mam ciąg bajtów:Chunking bytes (not strings) w Pythonie 2 i 3

data = b'abcdefghijklmnopqrstuvwxyz' 

chcę czytać te dane w kawałki n bajtów. Pod Pythonie 2, to jest trywialne stosując niewielką modyfikację do grouper receptury z dokumentacji itertools:

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)) 

Mając to na miejscu, mogę zadzwonić:

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

a otrzymasz:

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

W Pythonie 3 robi się to trudniejsze. grouper funkcja jak napisane prostu przewraca:

>>> 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 

A to dlatego, że w Pythonie 3, gdy iteracyjne nad bytestring (jak b'foo'), można uzyskać listę liczb, zamiast listy bajtów:

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

pyton 3 bytes funkcja pomoże tutaj:

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)) 

Korzystanie że mam wh co chcę:

>>> 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'] 

Ale (! oczywiście) funkcja bytes pod Python 2 nie zachowują ten sam sposób. To tylko aliasem str, tak że wyniki w:

>>> 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')"] 

... co wcale nie jest pomocne. Skończyło się na piśmie, co następuje:

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)) 

To wydaje się działać, ale jest to naprawdę sposób to zrobić?

+0

@AnttiHaapala, dzięki za wskazówkę. – larsks

Odpowiedz

3

Funcy (biblioteka oferuje szereg przydatnych narzędzi, wspierając zarówno Python 2 i 3) oferuje chunks function że robi dokładnie to:

>>> 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 

Alternatywnie, można to proste wdrożenie tego w programie (zgodny z obu Python 2.7 i 3):

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

Zachowuje się tak samo (przynajmniej dla danych; Funcy na chunks także współpracuje z iteratorów, to nie robi):

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

Krótko mówiąc: Tak, Funcy działa z iteratorami, ale działa podobnie jak "grouper" z pytania. Jeśli dane OP są iteratorem/strumieniem, Funcy nie jest rozwiązaniem. – vaultah

+0

OK, ale źródło nie może być iterowalne w funkcji 'chunked' – vlk

2

Korzystanie bytes z bytearray będzie pracować dla zarówno jeśli długość łańcucha była podzielna przez n lub przekazać non pusty łańcuch jako fillvalue:

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:

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:

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'] 

Jeśli zdałeś pusty ciąg można je odfiltrować:

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)) 

który będzie działał na dowolny ciąg znaków.

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']