Портирование на Python 3

Django 1.5 – это первая версия Django, поддерживающая Python 3. Ваш код может работать с обеими версиями Python: Python 2 (≥ 2.6.5) и Python 3 (≥ 3.2), благодаря приложению six.

Этот документ предназначен в первую очередь для авторов повторно используемых (подключаемых) приложений, которые хотят обеспечить поддержку как Python 2, так и Python 3. Он также описывает принципы написания совместимого кода на Django.

Философия

Предполагается, что вы знакомы с отличиями версий Python 2 и 3. Если это не так, сначала прочтите руководство по портированию. Также будет нелишним освежить ваши знания о строках unicode; хорошим способом сделать это является просмотр презентации the Pragmatic Unicode presentation.

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

Написание совместимого кода будет намного легче, если вы используете Python ≥ 2.6. Django 1.5 включает некоторые инструменты для совместимости приложений, такие как six module. Для удобства, некоторые псевдонимы были включены уже в Django 1.4.2. Если ваше приложение использует эти инструменты, вам потребуется версия Django ≥ 1.4.2.

Очевидно, написание совместимого кода потребует дополнительных сил, что может привести к разочарованию. Разработчики Django обнаружили, что написание кода на Python 3, который был бы совместим с Python 2, всё же приносит больше пользы, чем наоборот. Это не только сделает ваш код более перспективным, но и сделает доступными преимущества Python 3 (такие как более разумная обработка строк). Работа с Python 2 требует обратной совместимости, и мы как разработчики уже привыкли иметь дело с подобными ограничениями.

Инструменты портирования, предоставляемые Django, соответствуют представленной философии и будут описаны в данной инструкции.

Советы по портированию

Строки Unicode

Этот шаг заключается в следующем:

  • Добавление from __future__ import unicode_literals первой строкой в ваш модуль Python – будет лучше, если эта строка присутствует в каждом модуле, в противном случае вы должны проследить какой режим используется;

  • Удаление префикса u перед строками unicode;

  • Добавление префикса b перед байтовыми строками (bytestrings).

Систематическое выполнение этого шага гарантирует обратную совместимость.

Однако, в приложениях Django обычно не используются байтовые строки, поскольку Django предоставляет лишь интерфейс unicode для программистов. При использовании Python 3 не рекомендуется использовать байтовые строки, за исключением двоичных данных или байт-ориентированных интерфейсов. Python 2 делает байтовые строки и строки Unicode взаимозаменяемыми, если они содержат только ASCII данные. Воспользуйтесь этим, чтобы использовать строки Unicode там, где это возможно, и избежать добавления префиксов b.

Примечание

Префикс u из Python 2 в версии Python 3.2 вызовет синтаксическую ошибку, но это будет исправлено в Python 3.3 благодаря PEP 414. Таким образом, это преобразование опционально в Python ≥ 3.3. Это всё ещё рекомендуется в философии Python 3 “write Python 3 code”.

Обработка строк

Тип строки unicode из Python 2 был переименован в тип str для Python 3, str()` был переименован в bytes, исчез тип basestring. Библиотека six предоставляет инструменты, учитывающие эти изменения.

Django также содержит несколько связанных классов и функций в модулях django.utils.encoding и django.utils.safestring. Их имена используют тип str, который неодинаков в Python 2 и Python 3, а также unicode, которого попросту нет в Python 3. Для того, чтобы избежать неоднозначности и путаницы они были переименованы в bytes и text.

Это наименования, которые были изменены в django.utils.encoding:

Прежнее имя

Новое имя

smart_str smart_bytes
smart_unicode smart_text
force_unicode force_text

Для поддержания обратной совместимости прежние имена ещё работают на Python 2. В Python 3 smart_str является псевдонимом smart_text.

Для обеспечения дальнейшей совместимости новые наименования работают как в Django 1.4.2.

Примечание

django.utils.encoding был очень сильно изменён в Django 1.5 для улучшения API. Просмотрите соответствующую документацию для получения более подробной информации.

django.utils.safestring в основном используется через mark_safe() и mark_for_escaping() функции, которые не изменились. В случае, если вы используете что-то иное, см. таблицу соответствий:

Прежнее имя

Новое имя

EscapeString EscapeBytes
EscapeUnicode EscapeText
SafeString SafeBytes
SafeUnicode SafeText

Для поддержания обратной совместимости прежние имена ещё работают на Python 2. В Python 3, EscapeString и SafeString являются псевдонимами для EscapeText и SafeText соответственно.

Для обеспечения дальнейшей совместимости новые наименования работают как в Django 1.4.2.

__str__() и  __unicode__() методы

В Python 2 модель объекта имеет __str__() и  __unicode__() методы. Если эти методы существуют, они должны вернуть str (байт) и unicode (строку) соответственно.

Оператор print и str вызовет встроенный метод __str__() для удобочитаемого представления объекта. unicode вызовет метод  __unicode__(), если он существует, в противном случае вернёт метод __str__() и декодирует результат в соответствии с системными настройками. Базовый класс Model автоматически получит метод __str__() из  __unicode__() путем кодирования в UTF-8.

В python 3 есть объект __str__(), который должен вернуть str (строку текста).

(Также можно определить __bytes__(), но приложения Django практически не используют этот метод, потому что обычно не имеют дело с байтами.)

Django предоставляет простой способ определить __str__() и  __unicode__() методы, которые работают на Python 2 и 3: необходимо определить метод __str__(), возвращающий текст и применить декоратор python_2_unicode_compatible().

В Python 3, декоратор ничего не выполняет. В Python 2, он определяет соответствующие методы  __unicode__() и __str__() ( в процессе замены оригинального __str__()). Вот пример:

from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible
class MyClass(object):
    def __str__(self):
        return "Instance of my class"

Этот метод является наиболее подходящим для портирования в стиле Django.

Для обеспечения дальнейшей совместимости этот декоратор доступен с версии Django 1.4.2.

Наконец, отметим, что метод __repr__() должен возвращать str во всех версиях Python.

dict и dict-like классы

dict.keys(), dict.items() и dict.values() возвращают списки в Python 2 и итераторы в Python 3. QueryDict и dict-like классы, определенные в django.utils.datastructures ведут себя аналогично в Python 3.

six предоставляет инструменты, учитывающие эти изменения: iterkeys(), iteritems() и itervalues(). Вместе с Django поставляется недокументированная функция iterlists() для django.utils.datastructures.MultiValueDict и его подклассов.

HttpRequest и HttpResponse объекты

В соответствии с PEP 3333:

  • заголовки всегда являются строковыми объектами ( str objects),

  • поток ввода и вывода всегда является объектом байтов (bytes objects).

В частности, HttpResponse.content содержит bytes, что может стать источником проблем, если вы сравните его со str в ваших тестах. Предпочтительное решение в таком случае: полагаться на assertContains() и assertNotContains(). Эти методы принимают ответ со строками unicode в качестве аргументов.

Рекомендации по написанию кода

Следующие рекомендации применяются в исходных кодах Django. Также они рекомендуются для сторонних приложений, которые следуют аналогичной стратегии портирования.

Требования к синтаксису

Юникод (Unicode)

В Python 3 все строки по-умолчанию считаются строками юникода. Тип unicode из Python 2 аналогичен str в Python 3, а тип str становится типом bytes.

Вы не должны использовать префикс u перед строками юникода, поскольку подобный синтаксис вызовет ошибку в Python 3.2. И вы обязаны ставить префикс b перед объектом байта.

Чтобы в Python 2 появилось такое же поведение, всегда импортируйте в модулях unicode_literals из __future__:

from __future__ import unicode_literals

my_string = "This is an unicode literal"
my_bytestring = b"This is a bytestring"

Если вам нужны строки байтов в Python 2 и строки юникода в Python 3, используйте встроенную функцию str:

str('my string')

В Python 3, нет никаких автоматических преобразований между str и bytes, а модуль codecs стал более строгим. Метод str.decode() всегда возвращает тип bytes, в свою очередь bytes.decode всегда возвращает тип str. В следствие этого иногда может появиться такая необходимость:

value = value.encode('ascii', 'ignore').decode('ascii')

Будьте осторожны с index bytestrings.

Исключения

При вызове исключения используйте в качестве ключевого слово as:

try:
    ...
except MyException as exc:
    ...

Прежний синтаксис был удалён из Python 3:

try:
    ...
except MyException, exc:    # Don't do that!
    ...

Синтаксис повторного вызова исключений также подвергся изменениям. См. six.reraise().

Магические методы (Magic methods)

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

Итераторы (Iterators)

class MyIterator(six.Iterator):
    def __iter__(self):
        return self             # implement some logic here

    def __next__(self):
        raise StopIteration     # implement some logic here

Булевы выражения (Boolean evaluation)

class MyBoolean(object):

    def __bool__(self):
        return True             # implement some logic here

    def __nonzero__(self):      # Python 2 compatibility
        return type(self).__bool__(self)

Деление

class MyDivisible(object):

    def __truediv__(self, other):
        return self / other     # implement some logic here

    def __div__(self, other):   # Python 2 compatibility
        return type(self).__truediv__(self, other)

    def __itruediv__(self, other):
        return self // other    # implement some logic here

    def __idiv__(self, other):  # Python 2 compatibility
        return type(self).__itruediv__(self, other)

Специальные методы ищутся в классе, а не в объекте.

Написание совместимого кода с помощью six

six является канонической библиотекой для одновременной поддержки Python 2 и 3. Ознакомьтесь с его документацией!

Измененная версия six входит в поставку Django начиная с версии 1.4.2. Вы можете импортировать его как django.utils.six.

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

Обработка строк

Типы basestring и unicode были удалены в Python 3, а метод str изменён. Для проверки этих типов, используйте следующие идиомы

isinstance(myvalue, six.string_types)       # replacement for basestring
isinstance(myvalue, six.text_type)          # replacement for unicode
isinstance(myvalue, bytes)                  # replacement for str

Python ≥ 2.6 предоставляет тип bytes как псевдоним типа str, так что вам не нужен six.binary_type.

Тип long

Типа long больше не существует в Python 3. 1L вызовет синтаксическую ошибку. Используйте six.integer_types, чтобы проверить является ли число обычным целым или это число типа long:

isinstance(myvalue, six.integer_types)      # replacement for (int, long)

xrange

Если вы используете xrange в Python 2, то импортируйте и используйте six.moves.range. Также вы можете импортировать six.moves.xrange (это эквивалент six.moves.range), но первый вариант позволяет вам просто удалить только строку импорта при отказе от поддержки Python 2.

Перемещённые модули

Некоторые модули были переименованы в Python 3. Модуль ``django.utils.six.moves``(на основе six.moves module) обеспечивает совместимость с ними и вы можете импортировать их.

PY2

Для получения разного кода при использовании Python 2 и Python 3, проверьте six.PY2:

if six.PY2:
    # compatibility code for Python 2

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

Модифицированная версия six

Версия six, идущая в поставке с Django(django.utils.six), имеет несколько дополнений только для внутреннего использования.