Установить кодировку в сценариях Python 3 CGI
При написании Python 3.1 CGI script я запускаю ужасные UnicodeDecodeErrors. Однако при запуске script в командной строке все работает.
Кажется, что open()
и print()
используют возвращаемое значение locale.getpreferredencoding()
, чтобы узнать, какую кодировку использовать по умолчанию. При запуске в командной строке это значение равно "UTF-8", как и должно быть. Но при запуске script через браузер кодировка таинственным образом переопределяется на "ANSI_X3.4-1968", который кажется просто причудливым именем для простого ASCII.
Теперь мне нужно знать, как сделать cgi script запуском с 'utf-8' в качестве кодировки по умолчанию во всех случаях. Моя настройка - Python 3.1.3 и Apache2 на Debian Linux. Системным языком является en_GB.utf-8.
Ответы
Ответ 1
Отвечая на это для поздних пользователей, потому что я не думаю, что опубликованные ответы попадают в корень проблемы, что является недостатком переменных среды локали в контексте CGI. Я использую Python 3.2.
-
open() открывает объекты файлов в текстовом (строковом) или двоичном (байтах) режиме для чтения и/или записи; в текстовом режиме кодировка, используемая для кодирования строк, записанных в файл, и декодирование байтов, считанных из файла, может быть указана в вызове; Если это не так, то определяется locale.getpreferredencoding(), который на linux использует кодировку из ваших настроек среды локали, которая обычно является utf-8 (например, LANG = en_US.UTF-8)
>>> f = open('foo', 'w') # open file for writing in text mode
>>> f.encoding
'UTF-8' # encoding is from the environment
>>> f.write('€') # write a Unicode string
1
>>> f.close()
>>> exit()
[email protected]:~$ hd foo
00000000 e2 82 ac |...| # data is UTF-8 encoded
-
sys.stdout на самом деле является файлом, открытым для записи в текстовом режиме с кодировкой на основе locale.getpreferredencoding(); вы можете написать строки просто отлично, и они будут закодированы в байтах на основе кодировки sys.stdout; print() по умолчанию записывает в sys.stdout - сам print() не имеет кодировки, а файл, который он пишет, имеет кодировку;
>>> sys.stdout.encoding
'UTF-8' # encoding is from the environment
>>> exit()
[email protected]:~$ python3 -c 'print("€")' > foo
[email protected]:~$ hd foo
00000000 e2 82 ac 0a |....| # data is UTF-8 encoded; \n is from print()
; вы не можете писать байты в sys.stdout - для этого используйте sys.stdout.buffer.write(); если вы попытаетесь записать байты в sys.stdout с помощью sys.stdout.write(), тогда он вернет ошибку, и если вы попытаетесь использовать print(), тогда print() просто превратит объект байтов в строковый объект и escape последовательность, подобная \xff
, будет рассматриваться как четыре символа \, x, f, f
[email protected]:~$ python3 -c 'print(b"\xe2\xf82\xac")' > foo
[email protected]:~$ hd foo
00000000 62 27 5c 78 65 32 5c 78 66 38 32 5c 78 61 63 27 |b'\xe2\xf82\xac'|
00000010 0a |.|
-
в CGI script вам нужно написать sys.stdout, и вы можете использовать функцию print(); но процесс CGI script в Apache не имеет настроек среды локали - они не являются частью спецификации CGI; поэтому по умолчанию sys.stdout кодируется по ANSI_X3.4-1968 - другими словами, ASCII; если вы попытаетесь напечатать() строку, содержащую символы, отличные от ASCII, до sys.stdout, вы получите "UnicodeEncodeError:" ascii "кодек не может кодировать символ...: порядковый номер не в диапазоне (128)"
-
простое решение - передать переменную среды LANG процесса Apache через CGI script с помощью команды Apache mod_env PassEnv в конфигурации сервера или виртуального хоста: PassEnv LANG; на Debian/Ubuntu убедитесь, что в файле /etc/apache 2/envvars вы раскомментировали строку "./etc/default/locale", чтобы Apache работал с языковым стандартом по умолчанию, а не с C (Posix), который также является ASCII кодирование); следующий CGI script должен работать без ошибок в Python 3.2:
#!/usr/bin/env python3
import sys
print('Content-Type: text/html; charset=utf-8')
print()
print('<html><body><pre>' + sys.stdout.encoding + '</pre>h€lló wörld<body></html>')
Ответ 2
Вы не должны читать ваши потоки ввода-вывода как строки для CGI/WSGI; они не являются строками Unicode, они явно байтовые последовательности.
(Считайте, что Content-Length
измеряется в байтах, а не в символах, представьте, что вы пытаетесь прочитать представление загрузки двоичного файла multipart/form-data
, свернутое в строки с расширением UTF-8 или возвращающее загрузку бинарного файла...)
Вместо этого используйте sys.stdin.buffer
и sys.stdout.buffer
для получения исходных потоков байтов для stdio и чтения/записи с ними. До уровня чтения форм для преобразования этих байтов в строковые параметры Юникода, где это необходимо, в зависимости от того, какая кодировка вашей веб-страницы имеет.
К сожалению, стандартные библиотеки CGI и WSGI-интерфейсов не соответствуют этому правилу в Python 3.1: соответствующие модули были грубо преобразованы из оригиналов Python 2 с использованием 2to3
, и, следовательно, существует ряд ошибок, которые в конечном итоге окажутся в UnicodeError.
Первой версией Python 3, которая может использоваться для веб-приложений, является 3.2. Использование 3.0/3.1 в значительной степени пустая трата времени. Потребовалось много времени, чтобы разобраться, и прошел PEP3333.
Ответ 3
Я решил проблему со следующим кодом:
import locale # Ensures that subsequent open()s
locale.getpreferredencoding = lambda: 'UTF-8' # are UTF-8 encoded.
import sys
sys.stdin = open('/dev/stdin', 'r') # Re-open standard files in UTF-8
sys.stdout = open('/dev/stdout', 'w') # mode.
sys.stderr = open('/dev/stderr', 'w')
Это решение не очень красивое, но, похоже, оно работает пока. Я фактически выбрал Python 3 для более распространенного v. 2.6 как мою платформу разработки из-за рекламируемой хорошей обработки Unicode, но пакет cgi
, кажется, разрушает некоторые из этих простот.
Я убежден, что файлы /dev/std*
могут отсутствовать в старых системах, у которых нет procfs. Тем не менее, они поддерживаются на последних Linux.
Ответ 4
Лучше всего явно кодировать строки Юникода в байтах, используя кодировку, которую вы хотите использовать. Опора на неявное преобразование приведет к таким неприятностям.
BTW: Если ошибка действительно UnicodeDecodeError, то она не происходит на выходе, она пытается декодировать поток байтов в Unicode, который будет происходить где-то еще.
Ответ 5
Подводя итог @cercatrova ответ:
- Добавьте строку
PassEnv LANG
в конец вашего /etc/apache2/apache2.conf
или .htaccess
.
- Uncomment
. /etc/default/locale
строка /etc/apache2/envvars
.
- Убедитесь, что в
/etc/default/locale
присутствует строка, похожая на LANG="en_US.UTF-8"
.