UnicodeDecodeError с использованием Django и форматированных строк

Я написал небольшой пример проблемы для всех, чтобы узнать, что происходит с использованием Python 2.7 и Django 1.10.8

# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals, print_function

import time
from django import setup
setup()
from django.contrib.auth.models import Group

group = Group(name='schön')

print(type(repr(group)))
print(type(str(group)))
print(type(unicode(group)))

print(group)
print(repr(group))
print(str(group))
print(unicode(group))

time.sleep(1.0)
print('%s' % group)
print('%r' % group)   # fails
print('%s' % [group]) # fails
print('%r' % [group]) # fails

Выход со следующим выходом + трассировка

$ python .PyCharmCE2017.2/config/scratches/scratch.py
<type 'str'>
<type 'str'>
<type 'unicode'>
schön
<Group: schön>
schön
schön
schön
Traceback (most recent call last):
  File "/home/srkunze/.PyCharmCE2017.2/config/scratches/scratch.py", line 22, in <module>
    print('%r' % group) # fails
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 11: ordinal not in range(128)

Кто-нибудь знает, что здесь происходит?

Ответ 1

Проблема заключается в том, что вы интерполируете UTF-8 bytestrings в строку Unicode. Строка '%r' - это строка Юникода, потому что вы использовали from __future__ import unicode_literals, но repr(group) (используемый заполнителем %r) возвращает байтовую строку. Для моделей Django repr() может включать данные Unicode в представлении, закодированные в байтах, используя UTF-8. Такие представления не являются безопасными для ASCII.

В вашем конкретном примере repr() на вашем экземпляре Group создается байтовая строка '<Group: sch\xc3\xb6n>'. Интерполяция в строку Unicode вызывает неявное декодирование:

>>> u'%s' % '<Group: sch\xc3\xb6n>'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 11: ordinal not in range(128)

Обратите внимание, что я не использовал from __future__ import unicode_literals в моем сеансе Python, поэтому строка '<Group: sch\xc3\xb6n>' не является объектом unicode, это объект str bytestring!

В Python 2 вам следует избегать смешивания строк Unicode и байтов. Всегда явно нормализуйте свои данные (кодирование Unicode в байтах или декодирование байтов в Unicode).

Если вы должны использовать from __future__ import unicode_literals, вы все равно можете создать bytestrings с помощью префикса b:

>>> from __future__ import unicode_literals
>>> type('')   # empty unicode string
<type 'unicode'>
>>> type(b'')  # empty bytestring, note the b prefix
<type 'str'>
>>> b'%s' % b'<Group: sch\xc3\xb6n>'  # two bytestrings
'<Group: sch\xc3\xb6n>'

Ответ 2

Мне было трудно найти общее решение вашей проблемы. __repr__() то, что я понимаю, должен возвращать str, любые усилия по изменению, которые, похоже, вызывают новые проблемы.

Относительно того, что метод __repr__() определен вне проекта, вы можете перегружать методы. Например

def new_repr(self):
    return 'My representation of self {}'.format(self.name)

Group.add_to_class("__repr__", new_repr)

Единственное решение, которое я могу найти, - это явно указать интерпретатору, как обрабатывать строки.

from __future__ import unicode_literals
from django.contrib.auth.models import Group

group = Group(name='schön')

print(type(repr(group)))
print(type(str(group)))
print(type(unicode(group)))

print(group)
print(repr(group))
print(str(group))
print(unicode(group))

print('%s' % group)
print('%r' % repr(group))
print('%s' % [str(group)])
print('%r' % [repr(group)])

# added
print('{}'.format([repr(group).decode("utf-8")]))
print('{}'.format([repr(group)]))
print('{}'.format(group))

Работа со строками в python 2.x - беспорядок. Надеюсь, что это привносит некоторый свет в то, как работать (что единственный способ найти) проблему.

Ответ 3

Я думаю, что реальная проблема в коде django.

Сообщалось шесть лет назад:

https://code.djangoproject.com/ticket/18063

Я думаю, что патч для django решит его:

def __repr__(self):
    return self.....encode('ascii', 'replace')

Я думаю, что метод repr() должен возвращать "7 бит ascii".

Ответ 4

Если это так, нам нужно переопределить метод unicode с помощью нашего настраиваемого метода. Попробуйте ввести код ниже. Это будет работать. Я протестировал его.

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

from django.contrib.auth.models import Group

def custom_unicode(self):
    return u"%s" % (self.name.encode('utf-8', 'ignore'))
Group.__unicode__ = custom_unicode

group = Group(name='schön')

# Tests
print(type(repr(group)))
print(type(str(group)))
print(type(unicode(group)))

print(group)
print(repr(group))
print(str(group))
print(unicode(group))

print('%s' % group)
print('%r' % group)  
print('%s' % [group])
print('%r' % [group])

# output:
<type 'str'>
<type 'str'>
<type 'unicode'>
schön
<Group: schön>
schön
schön
schön
<Group: schön>
[<Group: schön>]
[<Group: schön>]

Ссылка: https://docs.python.org/2/howto/unicode.html

Ответ 5

Я не знаком с Django. Ваша проблема, кажется, представляет текстовые данные в ASCI, который фактически находится в Юникоде. Попробуйте модуль unidecode в Python.

from unidecode import unidecode
#print(string) is replaced with 
print(unidecode(string))

Обратитесь Unidecode