Подтвердить что ты не робот

Установить кодировку в сценариях 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.

4b9b3361

Ответ 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".