веб-разработчик на python
0,0
рейтинг
7 января 2014 в 00:29

Разработка → Катастрофа Unicode в Python3 перевод

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


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



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


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

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



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


С другой стороны, в Python 2 у нас есть два строковых типа: str, который достаточен для любых целей и задач, ограниченных строками в кодировке ASCII + некоторыми неопределенными данными, превышающими интервал в 7 бит. Вместе с типом str у Python2 есть тип данных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. На первый взгляд это выглядит вполне разумно, но на самом деле это не так. Раньше интерпретатор имел реализации для операций над байтовыми и 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'')


Выглядит достаточно похоже? Вовсе нет, потому что в результате мы имеем совершенно разные типы данных у результата операции.
Один из них это кортеж строк, второй больше похож на массив целых чисел. Я уже писал об этом ранее и подобное состояние вызывает у меня страдания. Теперь написание кода на 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 я не знаю что такое провал, если это не он.
Перевод: Armin Ronacher
@GDApsy
карма
23,0
рейтинг 0,0
веб-разработчик на python
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (53)

  • –10
    Да если б кто-то это отредоктировал в плане форматирования текста было бы отлично, а то форматирование текстов это моя ахилесова пята.
    • –1
      > Armin Ronacher довольно известный разработчик
      Тире.
      > но обвинить его в истерике и ретроградстве не так-то просто, его возражения
      Вместо запятой необходимо ставить двоеточие.
      > Все труднее становиться вести
      tsya.ru/
      > Когда кто-либо начинает обсуждение поддержки Unicode в двух ветках Python — это весьма сложная тема.
      Чо? (Wat?.jpg)
      > С тех пор как мне пришлось сопровождать большое
      Возможно, здесь имеет смысл поставить запятую.
      > С другой стороны в Python 2 у нас есть
      Запятая.
      > интервал в 7 битов, а кроме него есть
      «А»? Мы уже сказали, что есть два типа, откуда «а»? Почему не «и»?
      > Присмотревшись к ситуации вы можете заметить
      Запятая.

      > а то форматирование текстов это моя ахилесова пята.
      Ваша ахиллесова пята это русский язык, а не форматирование текстов.

      Называйте меня буквоедом, занудой (али ещё чего придумаете), но текст, каждое второе предложение которого содержит ошибки (как стилистические, грамматические, так и орфографические), читать сложно.
      • +11
        Почему не в ЛС?
      • +1
        Что ж замечания в большей части справедливы, относительно пунктуации есть некоторые сомнения, спасибо
      • –5
        Да не нужно там тире! Наречие между подлежащим и сказуемым, а тире ставится в отсутствии связки. Последнее время я заметил, что на хабре втыкают это тире куда ни попадя. Вероятно, чтобы подчеркнуть свою грамотность. Но тире — это как специя. Хороша к месту.
        • +11
          Тире — это не специя, а знак препинания. Оно нужно там, где этого требуют правила русского языка. Если не нравится тире, можно переформулировать предложение.
        • 0
          Почему не нужно?.. «Тире ставится между подлежащим и сказуемым, выраженным существительным в именительном падеже (без связки).» Наречие тут вообще не причём.
  • +7
    Стоило бы добавить, что Армин «сломался») и перевел свои проекты на Python3 с выходом версии 3.3. В этой версии были учтены его пожелания (PEP414).
    • +5
      Судя по всему нет, так как PEP414 представлен в феврале 2012 г. и подтвержден тогда же, а троллит разработчиков он этим постом 5 января 2014 г.
      • +5
        Просто подписываюсь под каждым словом статьи!
        В конце декабря переводил библиотеку для работы с .kdb на 3 питон, в частности стоит 3.3, на нем и тестировал. Больше всего вызвало проблемы корректно обрабатывать b"" в новом питоне вместо старого доброго str во втором.
        • +8
          Ну а в u"" в старом питоне не напрягает? Мне кажется разработчики третьей версии правильно поступили с уникодом, сделав его типом по-умолчанию для строк. Если вам нужно работать с байтами, используйте тип bytes, разве это не логично?
      • +5
        Значит никак не уймется) Ну в принципе дело полезное пусть додавливает нормальный юникод в третьей версии.
    • +1
      Сейчас возникло движение за создание python2.8. Надеюсь, Гвидо это выдержит, и мы не получим маргинальных форков. Конечно переход на 3 идет намного, намного хуже, чем ожидалось. Я застал еще 1.x -> 2.x, это произошло в момент, все хотели безусловно лучший язык, но к 3 накопилась критическая масса проектов.
      • –1
        Ну вы и придумаете же сравнение. При переходе от 1.x к 2.x нужно было парочку-троечку багов у себя в программе поправить и всё. И полученный код при этом оставался работоспособных в обоих версиях. Python3 же — это, строго говоря, вообще другой язык с кучей несовместимых изменений. Причём таких, что писать python2/3 код возможно, но… весьма и весьма непросто.

        Это всё равно что сравнить переход с K&R C на ANSI C с переходом на C++.
      • +1
        Ну вообще разработчики Python признают что держать две ветки питона была не очень хорошей идеей. Из-за того что python 2.x работает особо никто на python 3.x не движется, опять же да там свои тараканы.
  • +53
    Армин упускает главную идею: есть две логические сущности — строки и массивы байт. Строки — это текст. То, что люди пишут друг другу. То, что пользователь читает на сайте (имено пользователь и именно читает, а не то, как это видит компьютер). А вот массивы байт — это просто массивы байт. С ними работают компьютеры. Компьютеры пишут массивы байт в сокеты, компьютеры считывают их из файлов на диске, и компьютеры же преобразуют их в строки, чтобы люди могли их прочитать.

    В Python 2 эти концепции были смешаны, в Python 3 — разделены. То, что часть старого функционала, которая опиралась на хаки с кодеками (encode/decode в JSON? вы серьёзно?) перестала работать, вполне закономерно. Но это не значит, что ради некрасивых хаков нужно отказаться от красивой концепции. И если для массивов байт нужны функции, аналогичные функциям для строк, то нужно просто перенести эти функции в Python 3, а не смешивать две идеи, разводя зоопарк с кучей кодирований, декодирований, __str__ и __unicode__, настройками системы и т.д.
    • –2
      Нифига они не разделены в Python 3. Или вы скажете, что у вас теперь u'\u0065\u0308' и u'\u00eb' — это теперь вдруг одно и то же? А с точки зрения «именно пользователя, который их именно читает» они неотличимы.

      Всё с чем вы работаете и в Python2 и в Python3 и в любом другом языке — это массивы байт (short'ов или long'ов иногда), но никак не строки. «То, с чем работает пользователь» — штука сложная и многогранная и я не буду так уж сильно удивлён если кому-то Unicode для этого не хватит и он придумает свою структуру на основе, ну скажем, JSON. А тогда ему и потребуется перекодировать из Unicode в JSON — и почему этот процесс не должен называться encode, а обратный ему — decode?

      То, что разработчики Python 3, с упорством, достойным лучшего применения, делают вид, что весь зоопарк с кучей кодирований, декодирований и прочего таки в природе есть его не отменяет. Имя файла — это у нас «то, что видит пользователь» или нет? А если это UNIX и эта строка вдруг не является валидной UTF-8 строкой — что тогда?
      • +2
        Вы передёргиваете. Моё послание было в том, что логический тип «строка» был придуман для передачи и обработки текстовой информации. Логическая строка состоит из набора логических символов — человеческих алфавитов, пунктуации, спецсимволов и, при необходимости, служебных невидимых символов. Это простая концепция, принятая в большинстве высокоуровневых языков программирования (из тех, что сходу вспомню, только в C тип «char» — числовой, а не символьный). Если хотите, это общепринятые понятия. Спорить с ними можно, но для этого должны быть действительно веские причины.
      • 0
        Всё с чем вы работаете и в Python2 и в Python3 и в любом другом языке — это массивы байт (short'ов или long'ов иногда), но никак не строки.


        Только это по-разному типизированные массивы байт.
        Массив байт, соответствующий представлению в памяти экземпляра какого-нибудь класса, типизирован как экземпляр этого класса.
        Массив байт, соответствующий представлению в памяти строки, типизирован как строка, причём строка в конкретной кодировке.
        Массив байт, соответствующий… кхм, массиву байт, не типизирован никак. Ну, или типизирован как массив байт.
      • 0
        В питон надо добавить нормализацию, да.

        Всё, с чем я работают в высокоуровневых языках — это массивы unicode codepoints. Это не только символы, но и не просто байты.

        А статья — ворчание на тему «как же так, почему я не могу говнокодить на python 3 как раньше?!?»
    • 0
      Знаете вам неплохо бы почитать вот эту статью russian.joelonsoftware.com/Articles/BacktoBasics.html
      Реально. Так-как строки всегда были байтами и никогда байтами быть не перестанут.
      • 0
        Статья о том, что важно знать весь технологический стек, от проектирования архитектуры до нюансов работы процессора. Аргументов, что строку нужно воспринимать просто как массив байт, я так и не увидел.

        Более того, статья прекрасно показывает, как быстро растёт сложность при усложнении задач. Хранение строки — просто. Конкатенация строк — уже сложнее. Разбор структур типа XML — ещё сложнее. Думать о том, в какой кодировке к вам пришёл документ при разборе структуры в поисках атрибута доменной области, — это уже чертовский трудно. Абстракция «строка» по крайней мере разделить проблему на две части: считывание байт из входного потока и приведение их к объекту в памяти, и дальнейшая обработка таких объектов с известной структурой.
        • –1
          Аргументов, что строку нужно воспринимать просто как массив байт, я так и не увидел.

          Не нужно, а стоит. И это там есть. Основная мысль про то что надо это про это помнить. Иначе можно словить много лулзов.
          • 0
            Честно говоря, я не понимаю, о чём мы спорим. Если вы хотели сказать, что важно знать, как работают ваши строки, то я с этим уже согласился. Я же хотел подчеркнуть другую идею, а именно то, что строки очень часто бывает полезно воспринимать как некую отдельную абстрактную сущность. Чтобы можно было сказать «объедини эти строки», а не «выдели столько-то памяти, скопируй строки в выделенный блок, поставь в конце нуль-терминанту». Это не значит, что не нужно знать, как такое «объедени строки» будет работать внутри и надеяться на лучшее. Но это значит, что при написании программ вы можете мыслить абстракциями более высокого уровня, концетрируясь на основной задаче, а не на выделении памяти или кодировке ваших строк.
            • 0
              Поясню Армин ругается на то, что сейчас после python 2.x существенно усложнили работу с однобайтной кодировкой. За счет как раз такого отделения строк в абстрактную сущность. Причем где эта работа со строками была вполне естественной. Вообще говоря эта проблема со строками была сразу начиная с выпуска первого python 3. Это является основным препятствием перехода на 3 ветку питона.
              • –1
                В Python 3 однобайтная кодировка — это как раз тип bytes. И если для него нет каких-то функций (которые были в 2-ке для str), то нужно просто добавить эти функции.

                Ну не знаю, для меня основной причиной неперехода на Python 3 является отсутсвие некоторых (довольно уникальных) библиотек и доисторический CentOS 5.9 на серверах, где системным является вообще Python 2.4. А вот как раз со строками лично мне в последнее время гораздо проще работать из Python 3.
                • 0
                  Как раз проблема что просто str на bytes не заменить.

                  А вот как раз со строками лично мне в последнее время гораздо проще работать из Python 3.

                  Если чисто со строками и они у вас изначально уникодные конечно проще.
  • –1
    Если честно b"" большой геморрой.

    С точки зрения теоритической логики это верно и круто, но не полностью, т.к., как указал автор, не все гладко с форматированием и byte совсем не str.
    На практике, короче говоря, проблемы.
    Да и не тем они занялись, разработчики Питона, выходит перфекционизм какой-то, для решения проблемы с юникодом нет ни одного стороннего проекта, для решения проблемы с тредингом и локом — 10ки проектов с финансированием!

    Только тут Гвидо говорит с этим все зашибись, хотя это не так, иначе не было бы Shedskin, Pypy, Stackless, Eventlet, Greenlet и десятков других проектов пытающихся поправить это недоразумение.

    Имхо если 4ка не получит честный, полноценный трединг и мультипроцессинг — то Питон так и останется скриптовым языком для простых решений и забавой ученых без скалируемых алгоритмов.
    • +8
      Вы смешиваете понятия языка программирования и его реализации. Python 2/Python 3 — это версии языка. Эти названия сами по себе обозначают некую спецификацию (даже если эта спецификация определяется основной реализацией). Гвидо, в первую очередь, занимается развитием языка. Оптимизации (в том числе многопоточность), как говорит сам Гвидо, — это дело реализации.

      Имхо если 4ка не получит честный, полноценный трединг и мультипроцессинг — то Питон так и останется скриптовым языком для простых решений и забавой ученых без скалируемых алгоритмов.

      О каком скалировании идёт речь? Научные библиотеки пишутся через C-расширения, где с потоками нет проблем (плюс плюшки от GPU, ага). Для ненаучных вполне можно использовать multiprocessing (который на Linux даёт не такой уж и большой оврехед по сравнению с потоками). В кластерных вычислениях производительность вообще чаще всего определяется каналом связи, скоростью считывания с диска или эффективностью алгоритмов, и очень редко опирается в линейную скорость работы интерпретатора (см., например, PySpark)
      • 0
        Я в целом о направлении разработки Питона. Для продвижения в корпоративных кругах вместо Явы проблема с разницей 2 и 3 слишком велика. Многие профессионалы Python 2 не хотят с 3-кой работать.

        Надо было депрекейтед сделать и старый str вывести в загружаемый модуль (как было сделано со string).
        • 0
          А зачем продвигать Python в корпоративных кругах? У него уже есть достаточно большое и дружелюбное сообщество, отличная документация и куча первоклассный библиотек. А популяризовать язык просто ради факта не вижу смысла.
    • +3
      На практике та модель, к которой пришел Питон в третьей версии, практикуется в Java, .NET, Obj-C и многих других современных языках уже много лет. И все проблемы с ней решаются очень просто — если вы работаете со строками, представленными в виде байт, то преобразуйте их к строке и работайте с ней, а потом обратно. А если это на самом деле и не строка вовсе, тогда с какой стати на ней должны работать стандартные строковые операции?
      • 0
        Мне кажется им стоило бы оставить оба метода. Для маленьких скриптов геморой с энкод-декод не стоит того.
        • +3
          А для больших скриптов неявные преобразования увеличивают вероятность ошибки.

          Ну и Zen of Python — «explicit is better than implicit».
  • +23
    Видимо Армин не очень часто сталкивается с начинающими разработчиками. Мало кто из них может сказать какой тип данных ожидается на вход функции — строка или юникод да и чем они черт возьми отличаются и зачем нужен юникод если есть строки. Это очень большая проблема для языка, который позиционируется (ну мне кажется, что он так позиционируется) как один из самых простых для восприятия языков. Так что я тут на стороне Гвидо — к чертям метод encode в «байтовых строках». Что можно энкодить в массиве байт? Господи, да люди, которые работают по полгода с питоном и не скажут какой метод используется для преобразования из строки в юникод — encode или decode.
    Короче я за лучшее восприятие и читаемость. И да, я за то, что питон 3 хорош и, что резкий переход от 2 к 3 сделал много полезного в самой концепции языка. Переход конечно муторный и болезненный и далеко не все перейдут в ближайшее время, но, мне кажется, это стоит того
    • +8
      Я могу ошибаться, но все же предположу, что основная проблема — в неправильной смысловой интерпретации методов encode && decode для str && unicode. Еще проблем добавляет проставленная по умолчанию кодировка. Если бы ее не было, а она всегда указывалась явно, то половина ошибок выглядела бы при чтении так: «я пытаюсь из байтов с кучей искейпов сделать utf8 при помощи ascii».

      Ну и всегда есть наводящие вопросы. Какой смысл у encode для str? Какой смысл у encode для unicode? И decode для них? Что они должны вернуть? Я лично до сих пор путаюсь. Поэтому вывод могу сделать только один: unicode в питоне несамоочевиден. Например, постоянные проблемы с чтением файлов, что проще использовать
      codecs.open("file", "r", "utf-8")
      

      чем преобразовать это руками. Или всякие там
      hashlib.md5(sentence.encode('utf8'))
      

      А иногда бывают и ужасы при работе с чужими либами:
      r['name'].encode('utf8').decode('string_escape')
      

      Ну и классика:
      mimetypes.guess_type(self.file.path.encode('utf8'))
      


      Не много ли encode'ов для таких простых вещей? Вроде же utf8 везде использую, но все равно приходится encodить или decodить. Поэтому теряется даже желание что-то там понимать. Чаще писать encode, чем decode. В особо плохих случаях, когда много непривычных escape'ов, писать decode. Смысл? Понимание? Не желаю. Поэтому совершенно согласен с python3, хотя, справедливости ради, там эти проблемы с кодировками иногда бывают еще более замороченные, когда что-то с escape'ами нужно делать.
    • 0
      Насколько я понимаю Армин и не выступает против перемен в языке как явления, и ситуацию с кодировками что в питоне3, что в питоне2 по его версии далека от идеала. Новички это довольно сложный аргумент: они чаще всего пользуются готовыми библиотеками, наляпать чтоб быстро работала, а дальше ситуация меня не интересует это аналогично новички, хотя и опытные в некоторых случаях такой подход используют. И с кодировками новичок редко сталкивается просто потому что этот вопрос на себя берет библиотека вместе с ее tutorial, но она должна быть еще написана, а автору библиотеки как раз обрабатывать различные кодировки нужно. Здесь дискуссия не об интересах новичков, они приспособятся к любому порядку использования, если он пригоден для человека. И многие элементы программы даже, вполне, наглядные будут для новичка выглядеть магией.
      • +6
        Ситуация далека от идеала, но скорее по причине изначальных проблем с этими сущностями во 2 питоне. Делать как говорит Армин — это перетаскивать эти проблемы в 3 питон.

        — Почему бы не вернуть эти методы преобразования кодировки(encode и decode) назад?
        — годами команда разработки Python не хочет вернуть возможность форматирования для байтовых строк.

        По-моему, нельзя возвращать эти функции, иначе будет такая же проблема — мало кто будет понимать различие строк и «байтовых строк». Опять же, я не считаю название «байтовые строки» подходящим. Это неизменяемый байтовый массив, и относиться к нему надо как к массиву байт. Мне нравится этот подход, Армину нет.
        Работаете с низкоуровневыми протоколами, которые возвращают бинарные данные? Да у вас байтовый массив. После преобразования это может быть текст или другие типы данных. Чем более четко разделяются типы данных тем меньше проблем в понимании как программа работает.
        Конечно, теперь будет больше перекодирования из байт в в строки и обратно, ведь раньше можно было просто взять бинарные данные, поработать с ними как с текстом и выплюнуть куда-нибудь наружу, сохранив тип «байтовой строки». Однако после выкатки в бой, внезапно можно обнаружить, что один раз из ста получаем на выходе юникод. Ну добавим просто в конце encode, зачем там особо разбираться, мало ли где у нас произошло неявное преобразование. Мне этот подход кажется плохим, я двумя руками за новый со сломом совместимости.
        • 0
          Я вполне с вами согласен, неявные преобразования в таком месте программы весьма опасны.
  • +2
    К сожалению, в реальном мире, все происходит по-другому: вы вынуждены регулярно работать с разными кодировками
    У меня другой реальный мир. С кодировками приходится работать крайне редко: это либо легаси-код, либо API старых сервисов.
    • +2
      Боюсь именно чтобы объяснить вашу ошибку и сделан python3. У вас совершенно нереальный мир, если в нем нет ни файлов ни передачи данных по сети. Любые данные извне программы приходят в некоторой кодировке, абсолютно все программы работают с кодировками.
      • 0
        Я просто немного не так выразился, я всегда работаю только с одной кодировкой: utf-8.
        И моя область деятельности — веб. Другим кодировкам там места нет.
        • +1
          Именно. utf-8 — это всего лишь цепочка байт, а не строка, объясняет вам python3.
          • +2
            И он совершенно прав. Потому что цепочка байт может быть и не utf-8, и вообще не строкой.

            Если это строка — то скажите об этом интерпретатору (decode). И вы получите юникодную строку, с которой можно работать. А потом, когда её нужно отдать обратно, сделайте encode. Таким образом, код, который работает со строками, не парится с кодировками — они фигурируют только на interop boundary, и при этом везде в явном виде. И в чем проблема?
            • 0
              Так вот эта статья что мы комментируем про «И в чем проблема?»
  • –2
    Если бы люди так сильно не привязывались, то и недовольных было бы меньше.
  • –1
    Все-таки большую свинью подложили разработчики компьюторов, когда за единицу адресации выбрали 8 бит. И 4 гирабайта для 32-битных систем, и разные типы строк.
    • +1
      Правильно, надо было выбрать 10 бит! Тогда бы никто не путался, переводя между килобайтами, мегабайтами и т.п. ;)
      • +1
        Похоже, что иронию, без выделения ее цветом, на хабре понимать перестали ;)
  • +1
    А что им надо было сделать?)
  • –10
    Стошнило от вашего перевода. И качества местных комментариев. Спасибо за ссылку, занятное чтиво.
  • +1
    В свете поста может быть полезен и этот материал, который стал уже довольно известным насколько я понимаю, так как на него есть ссылки в FAQ по Unicode в Python: Презентация о Unicode в Python 2\3 Посмотрел статью и комментарии, вроде бы ее не было.
  • 0
    У меня ощущение, что у Армина где-то жёсткая ошибка в проектировании и он не хочет в этом признаваться. Иначе, я не знаю, как это объяснить…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.