Опасности sys.setdefaultencoding('utf-8')

В Python 2 существует тенденция обескураживать установку sys.setdefaultencoding('utf-8'). Может ли кто-нибудь перечислить реальные примеры проблем с этим? Аргументы типа it is harmful или it hides bugs звучат не очень убедительно.

ОБНОВЛЕНИЕ. Обратите внимание, что этот вопрос относится только к utf-8, а не к изменению кодировки по умолчанию "в общем случае".

Приведите несколько примеров кода, если сможете.

Ответ 1

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

Резюме выводов

Основываясь на опыте и доказательствах, которые я собрал, вот выводы, к которым я пришел.

  • Настройка стандартного кодирования для UTF-8 в настоящее время безопасна, за исключением специализированных приложений, обрабатывающих файлы из систем, не поддерживающих unicode.

  • "Официальное" отклонение коммутатора основано на причинах, которые больше не соответствуют для подавляющего большинства конечных пользователей (а не для поставщиков библиотек), поэтому мы должны перестать препятствовать пользователям устанавливать его.

  • Работа с моделью, которая по умолчанию корректно обрабатывает Unicode, намного лучше подходит для приложений для межсистемных коммуникаций, чем вручную работать с API-интерфейсами Unicode.

Эффективно, очень часто изменяя кодировку по умолчанию , избегает ряда головных болей пользователя в подавляющем большинстве случаев использования, Да, бывают ситуации, когда программы, посвященные множественным кодировкам, будут молчаливо ошибочно, но поскольку этот переключатель можно включить по частям, это не проблема в коде конечного пользователя.

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


Поскольку эти утверждения в значительной степени противоположны официальной линии связи Python, я думаю, что объяснение этих выводов оправданно.

Примеры успешного использования модифицированного defaultencoding в дикой природе

  • Дэйв Мальком из Fedora полагал, что всегда правы. Он предложил после изучения рисков изменить распределение шириной def.enc. = UTF-8 для всех пользователей Fedora.

    Жесткий факт, хотя Python сломался, - это только поведение хеширования я указанное в списке, которое никогда не подхватывает какой-либо другой противник в основном сообществе в качестве причины для беспокоиться или даже того же человека при работе с пользовательскими билетами.

    Резюме Fedora: По общему признанию, само изменение было описано как "дико непопулярное" с основными разработчиками, и его обвинили в непоследовательности с предыдущими версиями.

  • Есть 3000 проектов в openhub. У них медленный интерфейс поиска, но, просматривая его, я считаю, что 98% используют UTF-8. Ничего не найдено о неприятных сюрпризах.

  • Есть 18000 (!) ветвей магистрали github с изменением.

    В то время как изменение " непопулярно, в основном сообществе его довольно популярны в пользовательской базе. Хотя это можно игнорировать, поскольку пользователи, как известно, используют хакерские решения, я не думаю, что это важный аргумент из-за моего следующего пункта.

  • Из-за этого существует только 150 bugreports всего на GitHub. При эффективном 100% изменение кажется положительным, а не отрицательным.

    Чтобы обобщить существующие проблемы, с которыми столкнулись люди, я просмотрел все вышеупомянутые билеты.

    • Общение def.enc. для UTF-8 обычно вводится, но не удаляется в процессе закрытия проблемы, чаще всего в качестве решения. Некоторые более крупные из них объясняют это как временное исправление, учитывая "плохую прессу", но гораздо реже журналисты ошибок просто рад о исправить.

    • Несколько (1-5?) проектов изменили свой код, выполнив преобразования типов вручную, чтобы им больше не нужно было изменять значение по умолчанию.

    • В двух случаях я вижу, что кто-то утверждает это с def.enc. установленный в UTF-8, приводит к полному отсутствию вывода полностью, не объясняя тестовую настройку. Я не смог проверить заявку, и я протестировал ее, и нашел обратное, чтобы быть правдой.

    • Один утверждаетего "система" может зависеть от ее изменения, но мы не узнаем, почему.

    • У одного (и только одного) была настоящая причина его избежать: ipython либо использует сторонний модуль, либо тест-бегун модифицировал свой процесс неконтролируемым образом (никогда не оспаривается тот факт, что изменение def.enc было поддержано его сторонниками только на время настройки интерпретатора, то есть когда "владеют" процессом).

  • Я нашел нулевое указание на то, что разные хэши "é" и u'é "вызывают проблемы в реальном коде.

  • Python не "ломает"

    После изменения настройки на UTF-8 никакая функция Python, охватываемая модульными тестами, не работает иначе, чем без коммутатора. Сам коммутатор не тестируется вообще.

  • На bugs.python.org сообщается о разочарованных пользователях

    Примеры здесь, здесь или здесь (часто связанные с официальной строкой предупреждения)

    Первый показывает, как установлен переключатель в Азии (сравните также с аргументом github).

  • Ian Bicking опубликовал свою поддержку, всегда позволяющую это поведение.

    Я могу постоянно поддерживать свои системы и коммуникации UTF-8, все будет лучше. Я действительно не вижу недостатка. Но почему Python делает это SO DAMN HARD [...] Я чувствую, что кто-то решил, что они умнее меня, но я не уверен, что верю им.

  • Martijn Fassen, опровергая Ian, признал, что ASCII, возможно, был ошибочным в первую очередь.

    Я считаю, что если, скажем, Python 2.5, поставляемый с кодировкой UTF-8 по умолчанию, это фактически ничего не сломает. Но если бы я сделал это для своего Python, у меня скоро возникли проблемы, когда я передал свой код кому-то еще.

  • В Python3 они не "практикуют то, что они проповедуют"

    Вопреки любому def.enc. изменить так жестко из-за кода, зависящего от окружающей среды, или косности, обсуждение здесь вращается вокруг Python3 с его 'unicode sandwich' парадигмой и соответствующими требуемыми неявными предположениями.

    Далее они создали возможности для написания допустимого кода Python3, например:

    >>> from 褐褑褒褓褔褕褖褗褘 import *        
    >>> def 空手(合氣道): あいき(ど(合氣道))
    >>> 空手(う힑힜('👏 ') + 흾)
    💔
    
  • DiveIntoPython рекомендует его.

  • В этой теме сам Guido советует a профессиональный конечный пользователь, чтобы использовать зависящую от процесса среду с коммутатором, установленным для "создания настраиваемой среды Python для каждого проекта".

    Основная причина, по которой разработчики стандартной библиотеки Python 2.x не хотят, чтобы вы могли установить кодировку по умолчанию в своем приложении, заключается в том, что стандартная библиотека написана с предположением о том, что кодировка по умолчанию установлена, и никакие гарантии относительно правильной работы стандартной библиотеки не могут быть сделаны при ее изменении. Для этой ситуации нет тестов. Никто не знает, что не удастся, когда. И вы (или, что еще хуже, ваши пользователи) вернетесь к нам с жалобами, если стандартная библиотека вдруг начнет делать то, чего вы не ожидали.

  • Jython предлагает изменить его на лету даже в модулях.

  • PyPy сделал не поддержка reload (sys) - но вернул его в пользовательский запрос в течение одного дня без вопросов. Сравните с " вы делаете это неправильно "отношение CPython, требование без доказательства это "корень зла".


Завершение этого списка Я подтверждаю, что можно построить модуль, который выдает сообщение , потому что измененной конфигурации интерпретатора делает следующее:

def is_clean_ascii(s):
    """ [Stupid] type agnostic checker if only ASCII chars are contained in s"""
    try:
        unicode(str(s))
        # we end here also for NON ascii if the def.enc. was changed
        return True
    except Exception, ex:
        return False    

if is_clean_ascii(mystr):
    <code relying on mystr to be ASCII>

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

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

Ответ 2

Поскольку вы не всегда хотите, чтобы ваши строки были автоматически декодированы в Unicode, или, что самое важное, ваши объекты Unicode автоматически закодированы в байтах. Поскольку вы запрашиваете конкретный пример, вот один из них:

Возьмите веб-приложение WSGI; вы создаете ответ, добавляя продукт внешнего процесса в список, в цикле, и этот внешний процесс дает вам кодированные байты UTF-8:

results = []
content_length = 0

for somevar in some_iterable:
    output = some_process_that_produces_utf8(somevar)
    content_length += len(output)
    results.append(output)

headers = {
    'Content-Length': str(content_length),
    'Content-Type': 'text/html; charset=utf8',
}
start_response(200, headers)
return results

Это здорово, прекрасно и работает. Но тогда ваш сотрудник приходит и добавляет новую функцию; вы также предоставляете метки, и они локализованы:

results = []
content_length = 0

for somevar in some_iterable:
    label = translations.get_label(somevar)
    output = some_process_that_produces_utf8(somevar)

    content_length += len(label) + len(output) + 1
    results.append(label + '\n')
    results.append(output)

headers = {
    'Content-Length': str(content_length),
    'Content-Type': 'text/html; charset=utf8',
}
start_response(200, headers)
return results

Вы протестировали это на английском и все еще работает, отлично!

Однако библиотека translations.get_label() фактически возвращает Unicode values ​​, а при переключении языкового стандарта метки содержат символы, отличные от ASCII.

Библиотека WSGI записывает эти результаты в сокет, и все значения Юникода автоматически зашифровываются для вас, так как вы устанавливаете setdefaultencoding() в UTF-8, но вы рассчитали всю длину. Это будет слишком коротким, поскольку UTF-8 кодирует все за пределами диапазона ASCII более чем с одним байтом.

Все это игнорирует возможность того, что вы фактически работаете с данными в другом кодеке; вы можете писать латинский-1 + Юникод, и теперь у вас есть неправильный заголовок длины и сочетание кодировок данных.

Если бы вы не использовали sys.setdefaultencoding(), исключение было бы поднято, и вы знали, что у вас есть ошибка, но теперь ваши клиенты жалуются на неполные ответы; в конце страницы отсутствуют байты, и вы не совсем знаете, как это произошло.

Обратите внимание, что в этом сценарии нет даже сторонних библиотек, которые могут или не могут зависеть от значения по умолчанию ASCII. Параметр sys.setdefaultencoding() является глобальным, применяя к всем код, запущенный в интерпретаторе. Насколько вы уверены, что в этих библиотеках нет проблем с неявным кодированием или декодированием?

То, что Python 2 кодирует и декодирует между типами str и unicode, неявно может быть полезным и безопасным, когда вы имеете дело только с данными ASCII. Но вам действительно нужно знать, когда вы смешиваете Unicode и байтовые данные строки случайно, вместо того, чтобы покрывать его глобальной кистью и надеяться на лучшее.

Ответ 3

В первую очередь: многие противники изменения по умолчанию enc утверждают, что его немой, потому что его даже изменение сравнения ascii

Я считаю, что справедливо ясно, что, подчиняясь первому вопросу, я вижу, что никто не защищает ничего, кроме отклонения от Ascii до UTF-8.

Пример setdefaultencoding ('utf-16'), как представляется, всегда просто выдвигается теми, кто выступает против его изменения; -)


С m = {'a': 1, 'é': 2} и файл 'out.py':

# coding: utf-8
print u'é' 

Тогда:

+---------------+-----------------------+-----------------+
| DEF.ENC       | OPERATION             | RESULT (printed)|            
+---------------+-----------------------+-----------------+
| ANY           | u'abc' == 'abc'       | True            |     
| (i.e.Ascii    | str(u'abc')           | 'abc'           |
|  or UTF-8)    | '%s %s' % ('a', u'a') | u'a a'          | 
|               | python out.py         | é               |
|               | u'a' in m             | True            |
|               | len(u'a'), len(a)     | (1, 1)          |
|               | len(u'é'), len('é')   | (1, 2) [*]      |
|               | u'é' in m             | False  (!)      |
+---------------+-----------------------+-----------------+
| UTF-8         | u'abé' == 'abé'       | True   [*]      |
|               | str(u'é')             | 'é'             |
|               | '%s %s' % ('é', u'é') | u'é é'          | 
|               | python out.py | more  | 'é'             |
+---------------+-----------------------+-----------------+
| Ascii         | u'abé' == 'abé'       | False, Warning  |
|               | str(u'é')             | Encoding Crash  |
|               | '%s %s' % ('é', u'é') | Decoding Crash  |
|               | python out.py | more  | Encoding Crash  |
+---------------+-----------------------+-----------------+

[*]: Результат принимает одно и то же. См. Ниже.

При просмотре этих операций изменение кодировки по умолчанию в вашей программе может выглядеть не так уж плохо, что дает вам результаты "ближе" к тому, чтобы иметь только данные Ascii.

Что касается поведения хеширования (in) и len(), вы получаете то же самое, что и в Ascii (подробнее о результатах ниже). Эти операции также показывают, что между строк юникода и байта существуют существенные различия, которые могут вызывать логические ошибки, если они игнорируются вами.

Как уже отмечалось: это процесс широкий вариант, поэтому у вас есть только один выстрел, чтобы выбрать его - вот почему разработчики библиотеки никогда не должны это делать никогда, а чтобы их внутренности были в порядке, поэтому что им не нужно полагаться на неявные преобразования python. Они также должны четко документировать то, что они ожидают и возвращают, и отрицают ввод, что они не пишут lib (например, функцию нормализации, см. Ниже).

= > Написание программ с этим параметром делает риском для других использовать модули вашей программы в своем коде, по крайней мере, без фильтрации ввода.

Примечание. Некоторые оппоненты утверждают, что def.enc. это даже системный вариант (через sitecustomize.py), но в последнее время во время программной контейнеризации (докера) каждый процесс можно запустить в идеальной среде без накладных расходов.


Что касается поведения хэширования и len():

Он сообщает вам, что даже с измененным def.enc. вы все еще не можете не знать о типах строк, которые вы обрабатываете в своей программе. u '' и '' - это разные последовательности байтов в памяти - не всегда, а в целом.

Поэтому при тестировании убедитесь, что ваша программа ведет себя правильно и с данными без Ascii.

Некоторые говорят о том, что хэши могут стать неравными при изменении значений данных - хотя из-за неявных преобразований операции "==" остаются равными - это аргумент против изменения def.enc.

Я лично не разделяю это, поскольку поведение хэширования остается таким же, как и без его изменения. До сих пор не было убедительного примера нежелательного поведения из-за этого параметра в процессе, который я 'сам'.

В целом, в отношении setdefaultencoding ( "utf-8" ): ответ, если его немой или нет, должен быть более сбалансированным.

Это зависит. В то время как это предотвращает сбои, например. при выполнении операций str() в операторе журнала - цена является более высокой вероятностью появления неожиданных результатов позже, поскольку неправильные типы делают ее более длинной в коде, правильное функционирование которого зависит от определенного типа.

Ни в коем случае это не должно быть альтернативой для изучения разницы между строками байтов и строками Unicode для вашего собственного кода.


Наконец, установка кодировки по умолчанию из Ascii не облегчает вам жизнь для обычных текстовых операций, таких как len(), нарезка и сравнение - вы должны предположить, что (байтовый) stringyfying все с UTF-8 для решения проблем здесь.

К сожалению, это вообще не так.

Результаты '==' и len() - это far более сложная проблема, чем можно было бы подумать, но даже с типом того же с обеих сторон.

W/o def.enc. изменено, "==" всегда не выполняется для не Ascii, как показано в таблице. С его помощью он работает - иногда:

Юникод действительно стандартизовал около миллиона символов мира и дал им число, но, к сожалению, нет однозначности в отношении 1:1 между глифами, отображаемыми пользователю в устройствах вывода, и символами, из которых они генерируются.

Чтобы мотивировать вас исследовать это: Имея два файла, j1, j2, написанные с той же программой с использованием той же кодировки, содержащей пользовательский ввод:

>>> u1, u2 = open('j1').read(), open('j2').read()
>>> print sys.version.split()[0], u1, u2, u1 == u2

Результат: 2.7.9 Хосе Хосе Ложно (!)

Используя функцию print в качестве функции в Py2, вы видите причину: К сожалению, существует два способа кодирования одного и того же символа: акцент "e":

>>> print (sys.version.split()[0], u1, u2, u1 == u2)
('2.7.9', 'Jos\xc3\xa9', 'Jose\xcc\x81', False)

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

Так даже в Py3:

>>> u1, u2 = open('j1').read(), open('j2').read()
>>> print sys.version.split()[0], u1, u2, u1 == u2

Результат: 3.4.2 Хосе Хосе Ложно (!)

= > Независимо от Py2 и Py3, фактически независимо от любого используемого вами языка вычислений: Чтобы написать качественное программное обеспечение, вам, вероятно, придется "нормализовать" все пользовательские ввод. Стандарт Unicode стандартизовал нормализацию. В Python 2 и 3 функция unicodedata.normalize является вашим другом.

Ответ 4

Пример реального слова # 1

Он не работает в модульных тестах.

Тест-бегун (nose, py.test,...) сначала инициализирует sys, а затем обнаруживает и импортирует ваши модули. К этому времени слишком поздно менять кодировку по умолчанию.

К тому же достоинству это не работает, если кто-то запускает ваш код в качестве модуля, так как сначала начинается их инициализация.

И да, смешение str и unicode и использование неявного преобразования только продвигает проблему дальше по строке.

Ответ 5

Мы должны знать одно:

Python 2 использует sys.getdefaultencoding() для декодирования/кодирования между str и unicode

conversion between str and unicode

поэтому, если мы изменим кодировку по умолчанию, будут возникать всевозможные несовместимые проблемы. например:

# coding: utf-8
import sys

print "你好" == u"你好"
# False

reload(sys)
sys.setdefaultencoding("utf-8")

print "你好" == u"你好"
# True

Дополнительные примеры:

Тем не менее, я помню, что есть какой-то блог, предлагающий использовать unicode, когда это возможно, и только битовую строку при работе с I/O. Я думаю, что если вы будете следовать этому соглашению, жизнь будет намного проще. Можно найти больше решений: