Как вы читаете файл внутри zip-файла как текст, а не байты?

Простая программа для чтения CSV-файла внутри zip-файла работает в Python 2.7, но не в Python 3.2.

$ cat test_zip_file_py3k.py 
import csv, sys, zipfile

zip_file    = zipfile.ZipFile(sys.argv[1])
items_file  = zip_file.open('items.csv', 'rU')

for row in csv.DictReader(items_file):
    pass

$ python2.7 test_zip_file_py3k.py ~/data.zip

$ python3.2 test_zip_file_py3k.py ~/data.zip
Traceback (most recent call last):
  File "test_zip_file_py3k.py", line 8, in <module>
    for row in csv.DictReader(items_file):
  File "/home/msabramo/run/lib/python3.2/csv.py", line 109, in __next__
    self.fieldnames
  File "/home/msabramo/run/lib/python3.2/csv.py", line 96, in fieldnames
    self._fieldnames = next(self.reader)
_csv.Error: iterator should return strings, not bytes (did you open the file 
in text mode?)

Таким образом, модуль csv в Python 3 хочет видеть текстовый файл, но zipfile.ZipFile.open возвращает zipfile.ZipExtFile, который всегда обрабатывается как двоичные данные.

Как заставить это работать в Python 3?


person Marc Abramowitz    schedule 11.04.2011    source источник


Ответы (4)


Я только что заметил, что Ответ Леннарта не работает с Python 3.1, но работает с Python 3.2. Они улучшили zipfile.ZipExtFile в Python 3.2 (см. примечания к выпуску). Эти изменения улучшают работу zipfile.ZipExtFile с io.TextWrapper.

Между прочим, это работает в Python 3.1, если вы раскомментируете хакерские строки ниже на monkey-patch zipfile.ZipExtFile, не то, чтобы я рекомендовал этот вид хакерства. Я включаю его только для того, чтобы проиллюстрировать суть того, что было сделано в Python 3.2, чтобы все работало хорошо.

$ cat test_zip_file_py3k.py 
import csv, io, sys, zipfile

zip_file    = zipfile.ZipFile(sys.argv[1])
items_file  = zip_file.open('items.csv', 'rU')
# items_file.readable = lambda: True
# items_file.writable = lambda: False
# items_file.seekable = lambda: False
# items_file.read1 = items_file.read
items_file  = io.TextIOWrapper(items_file)

for idx, row in enumerate(csv.DictReader(items_file)):
    print('Processing row {0} -- row = {1}'.format(idx, row))

Если бы мне пришлось поддерживать py3k ‹ 3.2, я бы выбрал решение в другой мой ответ.

person Marc Abramowitz    schedule 12.04.2011
comment
Обратите внимание, что поддержка режима «U» была удалена в версии 3.6: docs .python.org/3/library/zipfile.html#zipfile.ZipFile.open - person grisaitis; 03.08.2018

Вы можете обернуть его в io.TextIOWrapper.

items_file  = io.TextIOWrapper(items_file, encoding='your-encoding', newline='')

Должно сработать.

person Lennart Regebro    schedule 12.04.2011

И если вам просто нравится читать файл в строку:

with ZipFile('spam.zip') as myzip:
    with myzip.open('eggs.txt') as myfile:
       eggs = myfile.read().decode('UTF-8'))
person Arigion    schedule 26.06.2019

Ответ Леннарта находится на правильном пути (Спасибо, Леннарт, я проголосовал за ваш ответ), и он почти работает:

$ cat test_zip_file_py3k.py 
import csv, io, sys, zipfile

zip_file    = zipfile.ZipFile(sys.argv[1])
items_file  = zip_file.open('items.csv', 'rU')
items_file  = io.TextIOWrapper(items_file, encoding='iso-8859-1', newline='')

for idx, row in enumerate(csv.DictReader(items_file)):
    print('Processing row {0}'.format(idx))

$ python3.1 test_zip_file_py3k.py ~/data.zip
Traceback (most recent call last):
  File "test_zip_file_py3k.py", line 7, in <module>
    items_file  = io.TextIOWrapper(items_file, 
                                   encoding='iso-8859-1', 
                                   newline='')
AttributeError: readable

Проблема заключается в том, что первый обязательный параметр io.TextWrapper является буфером; не файловый объект.

Похоже, это работает:

items_file  = io.TextIOWrapper(io.BytesIO(items_file.read()))

Это кажется немного сложным, а также раздражает необходимость считывать в память целый (возможно, огромный) zip-файл. Есть ли лучший способ?

Вот он в действии:

$ cat test_zip_file_py3k.py 
import csv, io, sys, zipfile

zip_file    = zipfile.ZipFile(sys.argv[1])
items_file  = zip_file.open('items.csv', 'rU')
items_file  = io.TextIOWrapper(io.BytesIO(items_file.read()))

for idx, row in enumerate(csv.DictReader(items_file)):
    print('Processing row {0}'.format(idx))

$ python3.1 test_zip_file_py3k.py ~/data.zip
Processing row 0
Processing row 1
Processing row 2
...
Processing row 250
person Marc Abramowitz    schedule 12.04.2011
comment
Еще один ответ от меня описывает лучший способ, который работает в Python 3.2. - person Marc Abramowitz; 13.04.2011