Почему python-cgi выходит из строя в юникоде?

Если вы запускаете этот код в консоли - он работает хорошо (он на русском языке), но если он запускается как cgi на сервере Apache2 - он терпит неудачу: <type 'exceptions.UnicodeEncodeError'>: 'ascii' codec can't encode characters in position 8-9: ordinal not in range(128). Код:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import cgitb
cgitb.enable()

print "Content-Type: text/html;charset=utf-8"
print 
s=u'Nikolja \u043d\u0435 \u0421\u0430\u0440\u043a\u043e\u0437\u0438!'
print s#.encode('utf-8')

Да, решение состоит в том, чтобы раскомментировать .encode('utf-8'), но я трачу больше времени, чтобы понять, почему это происходит, и я не могу видеть ответ.

Ответ 1

При запуске с консоли Python может обнаруживать кодировку консоли и неявно преобразовывать Unicode, напечатанный на консоль, в эту кодировку. Он все равно может выйти из строя, если эта кодировка не поддерживает символы, которые вы пытаетесь распечатать. UTF-8 может поддерживать все символы Unicode, но другие общие кодировки консоли, такие как cp437 в Windows Windows, не работают.

Когда stdout не является консолью, Python 2.X по умолчанию использует ASCII, когда он не может определить консольную кодировку. Вот почему в веб-сегменте вы должны быть явным и сами кодировать свой вывод.

В качестве примера попробуйте выполнить следующий script с консоли и с вашего веб-сервера:

import sys
print sys.stdout.encoding

С консоли вы должны получить некоторую кодировку, но с веб-сервера вы должны получить None. Обратите внимание, что Python 2.X использует ascii, но Python 3.X использует utf-8, когда кодировка не может быть определена.

Проблема также может возникать на консоли при перенаправлении вывода. Этот script:

import sys
print >>sys.stderr,sys.stdout.encoding
print >>sys.stderr,sys.stderr.encoding

возвращает следующее при прямом запуске и перенаправлении stdout:

C:\>test
cp437
cp437

C:\>test >out.txt
None
cp437

Примечание stderr не было затронуто, так как оно не было перенаправлено.

Переменная окружения PYTHONIOENCODING может использоваться для переопределения стандартной кодировки stdout/stdin.

Ответ 2

Попробуйте применить кодеки utf-8 на stdin и stdout...

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import cgitb
import sys
import codecs

reload(sys)
sys.setdefaultencoding('utf-8')
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
# If you need input too, read from char_stream as you would sys.stdin
char_stream = codecs.getreader('utf-8')(sys.stdin)

cgitb.enable()

print "Content-Type: text/html;charset=utf-8"
print 
s=u'Nikolja \u043d\u0435 \u0421\u0430\u0440\u043a\u043e\u0437\u0438!'
print s.encode('utf-8')