Портирование с Python 2 на Python 3: "кодек utf-8 не может декодировать байт"
Привет, я попытался перенести этот маленький фрагмент на Python 3 из 2.
Python 2:
def _download_database(self, url):
try:
with closing(urllib.urlopen(url)) as u:
return StringIO(u.read())
except IOError:
self.__show_exception(sys.exc_info())
return None
Python 3:
def _download_database(self, url):
try:
with closing(urllib.request.urlopen(url)) as u:
response = u.read().decode('utf-8')
return StringIO(response)
except IOError:
self.__show_exception(sys.exc_info())
return None
Но я все еще получаю
utf-8 codec can't decode byte 0x8f in position 12: invalid start byte
Мне нужно использовать StringIO, поскольку это zip-файл, и я хочу проанализировать его с помощью этой функции:
def _parse_zip(self, raw_zip):
try:
zip = zipfile.ZipFile(raw_zip)
filelist = map(lambda x: x.filename, zip.filelist)
db_file = 'IpToCountry.csv' if 'IpToCountry.csv' in filelist else filelist[0]
with closing(StringIO(zip.read(db_file))) as raw_database:
return_val = self.___parse_database(raw_database)
if return_val:
self._load_data()
except:
self.__show_exception(sys.exc_info())
return_val = False
return return_val
raw_zip - это возвращение функции download_database
3 ответа
utf-8 не может декодировать произвольные двоичные данные.
utf-8 - это кодировка символов, которая может использоваться для кодирования текста (например, представленная как str
введите в Python 3 - последовательность кодов Unicode) в bytestring (bytes
type - последовательность байтов (маленькие целые числа в интервале [0, 255]) и декодирование обратно.
utf-8 - не единственная кодировка символов. Существуют кодировки, несовместимые с utf-8. Даже если .decode('utf-8')
не поднял исключения; это не значит, что результат правильный - вы можете получить mojibake, если для декодирования текста используете неправильную кодировку. См . Хороший способ получить кодировку / кодировку ответа HTTP в Python.
Ваш ввод представляет собой zip-файл - двоичные данные не являются текстовыми, и поэтому вам не следует пытаться декодировать их в текст.
Python 3 помогает вам находить ошибки, связанные со смешиванием двоичных данных и текста. Чтобы перенести код с Python 2 на Python 3, вы должны понимать различие между текстом (Unicode) и двоичными данными (байтами).
str
в Python 2 это строка байтов, которая может использоваться для двоичных данных и (закодированного) текста. Если не from __future__ import unicode_literals
настоящее; ''
литерал создает строку байтов в Python 2. u''
создает unicode
пример. На питоне 3 str
Тип Unicode. bytes
относится к последовательности байтов на Python 3 и Python 2.7 (bytes
это псевдоним для str
на Python 2). b''
создает bytes
экземпляр на обоих питонах 2/3.
urllib.request.urlopen(url)
возвращает файл-подобный объект (двоичный файл), вы можете передать его как есть, например, для декодирования удаленного сжатого содержимого на лету:
#!/usr/bin/env python3
import xml.etree.ElementTree as etree
from gzip import GzipFile
from urllib.request import urlopen, Request
with urlopen(Request("http://smarkets.s3.amazonaws.com/oddsfeed.xml",
headers={"Accept-Encoding": "gzip"})) as response, \
GzipFile(fileobj=response) as xml_file:
for elem in getelements(xml_file, 'interesting_tag'):
process(elem)
ZipFile()
требует seek()
файл и, следовательно, вы не можете передать urlopen()
непосредственно. Вы должны сначала загрузить контент. Вы могли бы использовать io.BytesIO()
, чтобы обернуть это:
#!/usr/bin/env python3
import io
import zipfile
from urllib.request import urlopen
url = "http://www.pythonchallenge.com/pc/def/channel.zip"
with urlopen(url) as r, zipfile.ZipFile(io.BytesIO(r.read())) as archive:
print({member.filename: archive.read(member) for member in archive.infolist()})
StringIO()
это текстовый файл Он хранит Unicode в Python 3.
Если все, что вас интересует, - это возврат обработчика потока из вашей функции (вместо необходимости декодировать контент), можете ли вы использовать BytesIO
вместо StringIO
:
from contextlib import closing
from io import BytesIO
from urllib.request import urlopen
url = 'http://www.google.com'
with closing(urlopen(url)) as u:
response = u.read()
print(BytesIO(response))
Ссылка, которую вы разместили, http://software77.net/geo-ip?DL=2
пытается скачать zip
файл, который является двоичным.
- Вы не должны конвертировать двоичный двоичный объект в
str
(просто используйтеBytesIO
) - Если у вас есть действительно веская причина для этого, используйте
latin-1
как декодер.