Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Авария Unicode в Python3

Anna | 15.06.2014 | нет комментариев
От переводчика: Armin Ronacher достаточно знаменитый разработчик в Python-сообществе(Flask,Jinia2,werkzeug).
Он достаточно давным-давно начал оригинальный крестовый поход вопреки Python3, но обвинить его в истерике и ретроградстве не так-то легко, его возражения продиктованы серьезным навыком разработки, он достаточно детально аргументирует свою точку зрения. Немножко о терминологии:
coercion я перевел как принудительное реформирование кодировок, а byte string как байтовые строки, так как термин «сырые» строки(raw string) все же обозначает несколько иное.
«Историческое» примечание: PEP 214 от Армина, где он предлагает меры по устранению задачи с юникодом предложен им даже в 2012 году, подтвержден тогда же, но судя по его посту от 5 янв. 2014 года, воз и нынче там

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

В данном посте я покажу на примере решений разработчиков языка и стандартной библиотеки,
что Python 2 отменнее подходит для работы с текстом и байтовыми строками.

С тех пор как мне пришлось сопровождать огромное число кода, тот, что напрямую работал с реформированием между байтовыми строками и Unicode, ухудшения, произошедшие в Python3, вызвали у меня много горечи. Исключительно меня нервируют материалы стержневой команды разработчиков python, которые призывают меня верить, что python 3 отменнее 2.7.

Модель представления текста

Основное отличие между Python 2 и Python 3 –базовые типы, существующие для работы со строками и байтовыми строками. В Python 3 мы имеем один строковый тип: str, тот, что хранит данные в Unicode, и два байтовых типа: bytes и bytearray.

С иной стороны, в Python 2 у нас есть два строковых типа: str, тот, что доволен для всяких целей и задач, ограниченных ASCII некоторыми неопределенными данными, превышающими промежуток в 7 бит, а помимо него есть тип данных unicode, равнозначный типу данных str Python 3. Для работы с байтами в Python 2 есть один тип:bytearray, взятый из Python 3. Присмотревшись к обстановки, вы можете подметить, что из Python 3 кое-что удалили: поддержку строковых данных не в юникоде.Компенсацией жертвоприношения стал хешируемый байтовый тип данных(bytes). Тип данных bytarray изменяемый, а следственно он не может быть хеширован. Я дюже редко, использую бинарные данные как ключи словаря, а потому вероятность либо неосуществимость хеширования бинарных данных не кажется мне дюже серьезной. Исключительно в Python 2, так как байты могут быть без каких-либо задач размещены в переменную типа str.

Утраченный тип

Из Python 3 исключают поддержку байтовых строк, которые в ветке 2.x были типом str. На бумаге в этом решении нет ничего плохого. С академической точки зрения строки, неизменно представленные в юникоде, это восхитительно. И это подлинно так, если целый мир — это ваш интерпретатор. К сожалению, в настоящем мире, все происходит по-иному, вы обязаны регулярно трудиться с различными кодировками, в этом случае подход Python 3 к работе со строками трещит по швам.

Буду Добросовестен перед вами: то как Python 2 обрабатывает Unicode провоцирует ошибки, и я всецело одобряю совершенствования обработки Unicode. Моя позиция в том, что, то как это делается в Python 3, является шагом назад и порождает еще огромнее ошибок, а потому я безусловно ненавижу трудиться с Python 3.

Ошибки при работе с Unicode

Раньше чем я погружусь в детали, мы обязаны осознать разницу поддержки Unicode в Python 2 и 3,
а так же то, отчего разработчики приняли решение поменять механизм поддержки Unicode.

Первоначально Python 2 как и многие иные языки до него создавался без поддержки обработки сток различных кодировок.
Строка и есть строка, она содержит байты. Это требовало от разработчиков правильно трудиться с разными
кодировками вручную. Это было абсолютно терпимо для многих обстановок. Многие годы веб-фреймворк Django
не работал с Unicode, а применял экстраординарно байтовые строки.

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

Подход к обработке строк, использующих определенную кодировку, в Python 2 достаточно примитивен:
Вы берете строку(байтовую), которую вы могли получить откуда желательно, а после этого преобразуете
ее из той кодировки, которая характерна для источника строки(метаданные, заголовки, иные)
в строку Unicode. Став Unicode строкой, она поддерживает все те же операции
что и байтовая, но сейчас она может беречь больший диапазон символов.
Когда вам нужно передать строку на обработку куда-либо еще, то вы вновь
преобразуете ее в ту кодировку, которая применяется принимающей стороной,
и перед нами опять байтовая строка

Какие же особенности связаны с таким подходом? Для того, Дабы это работало на ярусе ядра языка,
Python 2 должен предоставлять метод перехода из мира без Unicode в красивый мир с Unicode.
Это допустимо вследствие принудительному реформированию байтовых и небайтовых строк. Когда это происходит
и как данный механизм работает?

Стержневой момент заключается в том, что когда байтовая строка участвует в одной операции с Unicode строкой,
то байтовая строка преобразуется в Unicode строку при помощи неявного процесса декодирования строки, тот, что использует кодировку «по умолчанию». Данной кодировкой по умолчанию считается ASCII. Python предоставлял вероятность менять кодировку по умолчанию, применяя один модуль, но сейчас из модуля site.py удалили функции для метаморфозы кодировки по умолчанию, она устанавливается в ASCII. Если запустить интерпретатор с флагом -s, то функция sys.setdefaultencoding будет вам доступна и вы сумеете поэкспериментировать, Дабы узнать что произойдет, если вы выставите кодировкой по умолчанию UTF-8. В некоторых обстановках при работе с кодировкой по умолчанию могут появиться задачи:

1. неявное задание и реформирование кодировки при конкатенации:

>>> "Hello "   u"World"
u'Hello World'

Тут левая строка преобразуется, применяя кодировку «по умолчанию», в Unicode строку. Если строка содержит не ASCII символы, то при типичной обстановки выполнения программы реформирование останавливается с выбросом исключения UnicodeDecodeError, так как кодировка по умолчанию ASCII

2. Неявное задание и реформирование кодировки при сопоставлении строк


>>> "Foo" == u"Foo"
True

Это звучит опаснее чем есть на самом деле. Левая часть преобразуется в Unicode, а после этого происходит сопоставление. В случае, если левая сторона не может быть преобразована, интерпретатор выдает предупреждение, а строки считаются неравными(возвращается False в качестве итога сопоставления). Это абсолютно здоровое поведение, если даже при первом знакомстве с ним так не кажется.

3. Очевидное задание и реформирование кодировки как часть механизма с применением кодеков.

Это одна из особенно зловещих пророческой и особенно распостраненный источник всех неудач и недопониманий Unicode в Python 2. Для предоления задач в этой области в Python 3 предприняли безрассудный шаг, удалив способ .decode() у Unicode строк и способ .encode() у байтовых строк, это вызвало наибольшее недопонимание и досаду у меня. С моей точки зрения это дюже тупое решение, но мне много раз говорили что это я ничего не понимаю, возврата назад не будет.

Очевидное реформирование кодировки при работе с кодеками выглядит так:


>>> "foo".encode('utf-8')
'foo'

Это строка, видимо, является байтовой строкой. Мы требуем ее преобразовать в UTF-8. Само по себе эnо безрезультатно, так как UTF-8 кодек преобразует строку из Unicode в байтовую строку с кодировкой UTF-8. Как же это работает? UTF-8 кодек видит, что строка не является Unicode строка, а следственно вначале выполняется принудительное реформирование к Unicode. Пока «foo» только ASCII данные и кодировка по умолчанию ASCII, принудительное реформирование происходит удачно, а теснее позже этогоUnicode строка u«foo» преобразуется в UTF-8.

Механизм кодеков

Сейчас вы знаете что Python 2 имеет два подхода к представлению строк: байтами и Unicode. Реформирование между этими представлениями осуществляется при помощи механизма кодеков. Данный механизм не навязывает схему реформирования Unicode->byte либо на нее схожую. Кодек может изготавливать реформирование byte->byte либо Unicode->Unicode. Реально система кодеков может реализовывать реформирование между всякими типами Python. Вы можете иметь JSON кодек, тот, что изготавливает реформирование строки в трудный Python объект на ее основе, если сочтете, что такое реформирование вам нужно.

Такое расположение дел может вызвать задачи с пониманием механизма, начиная с его основ. Примером этого может быть кодек с наименованием ‘undefined’, тот, что может быть установлен в качестве кодировки по умолчанию. В этом случае всякие принудительные реформирования кодировок строк будут отключены:


>>> import sys
>>> sys.setdefaultencoding('undefined')

>>> "foo"   u"bar"
Traceback (most recent call last):
    raise UnicodeError("undefined encoding")
UnicodeError: undefined encoding

И как же в Python 3 решают задачу с кодеками? Python 3 удаляет все кодеки, которые не исполняют реформирования вида: Unicode<->byte, а помимо того теснее непотребные теперь способ байтовых строк .encode() и строковый способ .decode(). Это дюже дрянное решение, так как было дюже
много пригодных кодеков. Скажем дюже распространено применять реформирование с поддержкой hex кодека в Python 2:


>>> "\x00\x01".encode('hex')
'0001'  

Пока вы можете сказать, что в данном определенном случае задача может быть решена при помощи модуля сходственного binascii, но задача больше велика, модули с кодеками доступны отдельно. Скажем библиотеки, реализующие чтение из сокетов, применяют кодеки для частичного реформирования данных из потоков данных библиотеки zlib:


>>> import codecs
>>> decoder = codecs.getincrementaldecoder('zlib')('strict')
>>> decoder.decode('x\x9c\xf3H\xcd\xc9\xc9Wp')
'Hello '
>>> decoder.decode('\xcdK\xceO\xc9\xccK/\x06\x00 \xad\x05\xaf')
'Encodings'

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

 
>>> "Hello World".encode('zlib_codec')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' does not support the buffer interface

(Обратите внимание, что кодек сейчас именуется zlib_codec взамен zlib, так как Python 3.3 не сберег ветхих обозначений для кодеков)

А что произойдет, если мы вернем назад способ .encode() для байтовых строк скажем? Это легко проверить даже без хаков интерпретатора Python. Напишем функцию с аналогичным поведением:

import codecs

def encode(s, name, *args, **kwargs):
    codec = codecs.lookup(name)
    rv, length = codec.encode(s, *args, **kwargs)
    if not isinstance(rv, (str, bytes, bytearray)):
        raise TypeError('Not a string or byte codec')
    return rv

Сейчас мы можем применять эту функцию как замену способа .encode() байтовых строк:

>>> b'Hello World'.encode('latin1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'bytes' object has no attribute 'encode'

>>> encode(b'Hello World', 'latin1')
Traceback (most recent call last):
  File "<stdin>", line 4, in encode
TypeError: Can't convert 'bytes' object to str implicitly

Ага! Python 3 теснее может трудиться с такой обстановкой. Мы получаем прекрасное оповещение об ошибке. Я считаю, что даже “Can’t convert ‘bytes’ object to str implicitly” значительно отменнее и внятней чем “’bytes’ object has no attribute ‘encode’”.

Отчего бы неворотить эти способы реформирования кодировки(encode и decode) назад? Я подлинно не знаю и не думаю огромнее об этом. Мне теснее неоднократно поясняли что я ничего не понимаю и я не понимаю новичков, либо, то что «текстовая модель» изменилась и мои требования к ней бессмысленны.

Байтовые строки утрачены

Сейчас следом за регрессией системы кодеков изменились и строковые операции: они определены лишь для строк Unicode. На 1-й взор это выглядит абсолютно умно, но на самом деле это не так. Прежде интерпретатор имел реализации для операций над байтовыми и Unicode строками. Данный подход был абсолютно явствен дял программистов, если объекту необходимо было иметь представление в виде байтовой либо Unicode строки, определялось два способа:__str__ and __unicode__. Да, безусловно, применялось принудительное метаморфоза кодировки, которое смущало новичков, но но у нас был выбор.

Отчего это благотворно? Потому что, к примеру, если вы трудитесь с низкоуровневыми протоколами, вам Зачастую нужно иметь дело с числами в определенном фоормате внутри байтовой строки.

Собственная система контроля версий, применяемая разработчиками Python, не работает на Python 3, потому что годами команда разработки Python не хочет воротить вероятность форматирования для байтовых строк.

Все вышеописанное показывает: модель обработки строковых данных Python 3 не работает в настоящем мире. К примеру в Python 3 «обновили» некоторые API, сделав их работающими только с Unicode, а потому они всецело непригодны для использования в реальных рабочих обстановках. К примеру сейчас вы не можете огромнее исследовать байты с поддержкой стандартной библиотеки, но только URL. Повод этого в неявном предположении, что все URL представлены лишь в Unicode(при таком расположении дел вы теснее не сумеете трудиться с почтовыми сообщениями в не Unicode помимо как всецело игнорируя существование бинарных вложений в письмо).

Прежде такое было достаточно легко поправить, но так как нынче байтовые строки утрачены для разработчиков, библиотека обработки URL имеет нынче две реализации. Одна для Unicode, а вторая для байтовых объектов. Две реализации для одной и той же функции ведут к тому что итог обработки данных может быть дюже различным:


>>> from urllib.parse import urlparse
>>> urlparse('http://www.google.com/')
ParseResult(scheme='http', netloc='www.google.com',
            path='/', params='', query='', fragment='')
>>> urlparse(b'http://www.google.com/')
ParseResultBytes(scheme=b'http', netloc=b'www.google.com',
                 path=b'/', params=b'', query=b'', fragment=b'')

Выглядит довольно схоже? Совсем нет, потому что в итоге мы имеем абсолютно различные типы данных у итога операции.
Один из них это кортеж строк, 2-й огромнее схож на массив целых чисел. Я теснее писал об этом ранее и сходственное состояние вызывает у меня терзания. Сейчас написание кода на Python доставляет мне солидный дискомфорт либо становится весьма неэффективным, так как вам сейчас доводится пройти через огромное число реформирований кодировок данных. Из-за этого становится дюже трудно писать код, реализующий все нужные функции. Идея что все Unicode дюже отменна в теории, но всецело неприменима на практике.

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

Наши костыли не работают

Помощь Unicode в ветке 2.х неидеальна и далека от идеала. Это отсутсвующие API, задачи, приходящие с различных сторон, но мы как программисты делали все это рабочим. Многие способы, которыми мы это делали ранее огромнее немыслимо применить в Python 3, а некоторые API будут изменены, Дабы отлично трудиться с Python 3.

Мой любимй пример это обработка файловых потоков, которые могли быть как байтовыми, так и текстовыми, но не было верного способа определить какой перед нами тип потока. Трюк, тот, что я помог популяризировать это чтение нулевого числа байт из потока для определения его типа. Сейчас данный трюкне работает. К примеру передача объекта запроса библиотеки urllib функции Flask, которая обрабатывает JSON, не работает в Python 3, но работает в Python 2:

>>> from urllib.request import urlopen
>>> r = urlopen('https://pypi.python.org/pypi/Flask/json')
>>> from flask import json
>>> json.load(r)
Traceback (most recent call last):
  File "decoder.py", line 368, in raw_decode
StopIteration

В ходе обработки выбрашенного исключения выбрасывается еще одно:

 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: No JSON object could be decoded

И что же?

Помимо тех задач, что я описал выше у Python 3 с помощью Unicode есть и куча других задач. Я начал отписываться от твиттеров разработчиков Python потому что мне наскучило читать какой Python 3 восхитительный, так как это противоречит моему навыку. Да, в Python 3 много плюшек, но то как поступили с обработкой байтовых строк и Unicode к ним не относится.

(Дрянней каждого то, что многие подлинно резкие вероятности Python 3 традиционно столь же отлично работают и в Python 2. Скажем yield from, nonlocal, помощь SNI SSL и т.д. )

В свете того, что только 3% разработчиков Python энергично применяют Python 3, а разработчики Python в Twitter громогласно заъявляют что миграция на Python 3 идет как и планировалось, я испытываю разочарование, так как детально описывал свой навык с Python 3 и как от последнего хочется избавиться.

Я не хочу это делать теперь, но хочу, Дабы команда разработчиков Python 3 чуть огромнее прислушалась к суждению сообщества. Для 97% из нас, Python 2, уютненький мирок, в котором мы трудились годами, а потому достаточно болезненно понимается обстановка, когда к нам приходят и заъявляют: Python 3 — очарователен и это не обсуждается. Это и легко не так в свете множества регрессий. Совместно с теми людьми, которые начинают обговаривать Python 2.8 и Stackless Python 2.8 я не знаю что такое провал, если это не он.

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB