Установить кодировку в скриптах Python 3 CGI - PullRequest
20 голосов
/ 17 февраля 2012

При написании Python 3.1 CGI-скрипта я сталкиваюсь с ужасными UnicodeDecodeErrors.Однако при запуске сценария в командной строке все работает.

Кажется, что open() и print() используют возвращаемое значение locale.getpreferredencoding(), чтобы знать, какую кодировку использовать по умолчанию.При запуске в командной строке это значение равно «UTF-8», как и должно быть.Но при запуске сценария через браузер кодировка загадочным образом переопределяется в «ANSI_X3.4-1968», что выглядит просто причудливым именем для простого ASCII.скрипт cgi во всех случаях запускается с utf-8 в качестве кодировки по умолчанию.Моя установка - Python 3.1.3 и Apache2 в Debian Linux.Общесистемный языковой стандарт en_GB.utf-8.

Ответы [ 5 ]

17 голосов
/ 24 октября 2013

Ответ на этот вопрос для опоздавших, потому что я не думаю, что опубликованные ответы дойдут до корня проблемы, а именно отсутствия региональных переменных среды в контексте CGI.Я использую Python 3.2.

  1. 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()
    user@host:~$ hd foo
    00000000  e2 82 ac      |...|    # data is UTF-8 encoded
    
  2. sys.stdout фактически является файлом, открытым для записи в текстовом режиме с кодировкой, основанной на locale.getpreferredencoding ();вы можете писать строки в него просто отлично, и они будут кодироваться в байты на основе кодировки sys.stdout;print () по умолчанию записывает в sys.stdout - сама print () не имеет кодировки, скорее это файл, в который она пишет, имеет кодировку;

    >>> sys.stdout.encoding
    'UTF-8'                          # encoding is from the environment
    >>> exit()
    user@host:~$ python3 -c 'print("€")' > foo
    user@host:~$ 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

    user@host:~$ python3 -c 'print(b"\xe2\xf82\xac")' > foo
    user@host:~$ 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                                                |.|
    
  3. в CGI-скрипте, который необходимо записать в sys.stdout, и вы можете использоватьprint (), чтобы сделать это;но процесс сценария CGI в Apache не имеет настроек среды локали - они не являются частью спецификации CGI;поэтому кодировка sys.stdout по умолчанию равна ANSI_X3.4-1968 - другими словами, ASCII;если вы попытаетесь вывести () строку, содержащую не-ASCII-символы, в sys.stdout, вы получите «UnicodeEncodeError: кодек« ascii »не может кодировать символ ...: порядковый номер не в диапазоне (128)»

  4. простое решение - передать переменную среды LANG процесса Apache в сценарий CGI с помощью команды Apache mod_env PassEnv в конфигурации сервера или виртуального хоста: PassEnv LANG;в Debian / Ubuntu убедитесь, что в / etc / apache2 / envvars вы раскомментировали строку ". / etc / default / locale", чтобы Apache работал с системным языком по умолчанию, а не с языком C (Posix) (который также является ASCII).кодирование);следующий скрипт CGI должен работать без ошибок в Python 3.2:

    <code>#!/usr/bin/env python3
    import sys
    print('Content-Type: text/html; charset=utf-8')
    print()
    print('<html><body><pre>' + sys.stdout.encoding + '
    h € lló wörld ')

4 голосов
/ 18 февраля 2012

Вы не должны читать свои потоки ввода-вывода как строки для 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 голосов
/ 17 февраля 2012

Я решил свою проблему с помощью следующего кода:

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.

2 голосов
/ 30 мая 2017

Обобщая ответ @cercatrova:

  • Добавьте PassEnv LANG строку в конец вашей /etc/apache2/apache2.conf или .htaccess.
  • Раскомментируйте . /etc/default/locale строку в /etc/apache2/envvars.
  • Убедитесь, что в /etc/default/locale.
  • sudo service apache2 restart
имеется строка, аналогичная LANG="en_US.UTF-8".
1 голос
/ 17 февраля 2012

Лучше всего явно кодировать строки Unicode в байты, используя кодировку, которую вы хотите использовать. Опора на неявное преобразование приведет к таким неприятностям.

Кстати: если ошибка действительно UnicodeDecodeError, то она не возникает на выходе, она пытается декодировать поток байтов в Unicode, что может произойти где-то еще.

...