Портирование на 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!
    ...

Синтаксис reraise-исключений также подвергся изменениям. См. 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

Импортируйте six.moves.xrange всюду, где вы использовали xrange.

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

Некоторые модули были переименованы в 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, имеет несколько дополнений.

assertRaisesRegex(testcase, *args, **kwargs)

Это заменяет testcase.assertRaisesRegexp в Python 2, и testcase.assertRaisesRegex в Python 3. assertRaisesRegexp всё ещё доступен в текущей версии Python3, но выдаст предупреждение.

assertRegex(testcase, *args, **kwargs)

Это заменяет testcase.assertRegexpMatches в Python 2, и testcase.assertRegex в Python 3. assertRegexpMatches всё ещё доступен в текущей версии Python3, но выдаст предупреждение.

В дополнение к инструментам six Django предоставляет thread как _thread и dummy_thread как _dummy_thread.