Перенос кода Python 2 в код Python 3

Автор:Brett Cannon

Аннотация

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

Если требуется порт модуля расширения вместо чистого кода Python, см. раздел Перенос модулей расширения на Python 3.

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

Для получения справки по портированию можно отправить python-porting список рассылки по электронной почте с вопросами.

Короткое объяснение

Заставить ваш проект быть совместимым единственным источником Python 2/3, основные шаги are:

  1. Беспокоитесь только о поддержке Python 2.7
  2. Убедитесь, что у вас хорошее покрытие теста (coverage.py может помочь; pip install coverage)
  3. Узнайте о различиях между Python 2 и 3 #. Используйте Futurize (или Modernize) для обновления код (например, pip install future)
  4. Используйте Pylint, чтобы убедиться, что вы не регрессируете в поддержке Python 3 (pip install pylint)
  5. Используйте caniusepython3, чтобы узнать, какие из ваших зависимостей блокируют использование Python 3 (pip install caniusepython3)
  6. Как только ваши зависимости перестанут блокировать вас, используйте непрерывную интеграцию, чтобы убедиться, что вы поддерживаете совместимость с Python 2 и 3 (tox можете помочь протестировать на наличие нескольких версий Python; pip install tox)
  7. Рассмотрите использование дополнительной статической проверки типа, чтобы удостовериться ваши работы использования типа в обоих Python 2 & 3 (например, использовать mypy, чтобы проверить вашу печать под обоими Python 2 & Python 3).

Подробнее

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

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

Имейте в виду эти ключевые моменты, пока вы читаете о деталях портирования вашего код для поддержки Python 2 и 3 одновременно.

Отказаться от поддержки Python 2.6 и старше

В то время как вы можете сделать Python 2.5 работать с Python 3, это много проще, если вам нужно работать только с Python 2.7. Если удаление Python 2.5 не является опцией, то проект six может помочь вам одновременно поддерживать Python 2.5 и 3 (pip install six). Однако поймите, что почти все проекты, перечисленные в этом HOWTO, не будут доступны вам.

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

Но нужно стремиться только к поддержке Python 2.7. Python 2.6 больше не поддерживается свободно и, таким образом, не получает багфиксов. Это означает, что вы придется работать над любыми проблемами, с которыми вы сталкиваетесь с Python 2.6. В этой HOWTO также упоминаются некоторые инструменты, которые не поддерживают Python 2.6 (например, Pylint), и это станет более обычным явлением с течением времени. Вам будет просто легче, если вы поддерживаете только те версии Python, которые вы должны поддерживать.

Убедитесь, что в файле setup.py указана надлежащая поддержка версий

В Вашем файле setup.py у вас должен быть надлежащий классификатор, определяющий, какие версии Python вы поддерживаете. Поскольку ваш проект еще не поддерживает Python 3, у вас должно, по крайней мере, быть определенный Programming Language :: Python :: 2 :: Only. В идеале также следует указать каждую основную/вспомогательную версию Python, которую вы поддерживаете, например Programming Language :: Python :: 2.7.

Хороший тестовый охват

После того, как у вас есть ваш код, поддерживающий самую старую версию Python 2, вы хотите убедиться, что ваш набор тестов имеет хорошее покрытие. Хорошее эмпирическое правило состоит в том, что, если вы хотите быть достаточно уверенными в своем наборе тестов, что любые неудачи, которые появляются после наличия инструментов, переписывают ваш код, фактические ошибки в инструментах а не в вашем код. Если вы хотите, чтобы число было нацелено на, попробуйте получить более 80% покрытия (и не чувствуйте себя плохо, если вам трудно получить лучшее, чем 90% покрытия). Если у вас еще нет инструмента для измерения покрытия теста, рекомендуется coverage.py.

Узнайте о различиях между Python 2 и 3

После того, как у вас будет ваш код хорошо протестирован, вы готовы начать портировать ваш код на Python 3! но чтобы полностью понять, как изменится ваш код и что вы хотите смотреть, пока вы кодируете, вы хотите узнать, какие изменения Python 3 вносит в терминах Python 2. Как правило, два лучших способа сделать это - прочитать документ Что нового» для каждого выпуска Python 3 и книга Портирование на Python 3 (который является бесплатным онлайн). Также есть удобная шпаргалка из проекта Python-Future.

Обновите свой кодекс

Как только вы почувствуете, что знаете, что отличается в Python 3 по сравнению с Python 2, пришло время обновить ваш код! в автоматическом портировании код есть выбор между двумя инструментами: Futurize и Modernize. Какой инструмент вы выберете, будет зависеть от того, насколько как Python 3 вы хотите, чтобы ваш код. Futurize делает все возможное, чтобы сделать Python 3 идиомы и практики существуют в Python 2, например, задняя тип bytes из Python 3, чтобы вы имели семантический паритет между основными версиями Python. Modernize, с другой стороны, является более консервативным и нацелен на подмножество Python 2/3 Python, прямо полагаясь на six, чтобы помочь обеспечить совместимость. Поскольку Python 3 - это будущее, было бы лучше рассмотреть Futurize, чтобы начать подстраиваться под любые новые практики, которые Python 3 вводит, к которым вы еще не привыкли.

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

К сожалению, инструменты не могут автоматизировать все, чтобы сделать ваш код работает под Python 3, и поэтому есть несколько вещей, которые вам потребуется обновить вручную, чтобы получить полную поддержку Python 3 (какие из этих шагов необходимы варьировать между инструментами). Прочитайте документацию для инструмента, который вы принимаете решение использовать, чтобы видеть то, что это фиксирует по умолчанию и что это может сделать произвольно, чтобы знать то, что (не) будет зафиксировано для вас и что вам, вероятно, придется зафиксировать самостоятельно (например, использующий io.open() по встроенной функции open() находится прочь по умолчанию в, модернизируют). К счастью, есть только пара вещей, которые можно рассматривать как большие проблемы, которые трудно отлаживать, если не смотреть.

Подразделение

В Python 3, 5 / 2 == 2.5 и не 2; все разделения между значениями int приводят к появлению float. Это изменение фактически было запланировано с Python 2.2, который был выпущен в 2002. С тех пор пользователям рекомендуется добавлять from __future__ import division во все файлы, которые используют операторы / и //, или запускать интерпретатор с флагом -Q. Если вы не делали этого, то вам нужно будет пройти через ваш код и сделать две вещи:

  1. Добавьте from __future__ import division в файлы
  2. Обновите любой оператор подразделения по мере необходимости, чтобы использовать // для использования разделения пола или продолжить использование / и ожидать поплавка

Причина, по которой / не просто переводится в // автоматически, заключается в том, что если объект определяет __truediv__ метод, но не __floordiv__, то ваш код начнет отказывать (например, определяемый пользователем класс, который использует / для обозначения некоторой операции, но не // для того же или вообще).

Текст в сравнении с двоичными данными

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

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

Для начала необходимо решить, какие API берут текст, а какие двоичные (рекомендуется высоко не проектировать API, которые могут принимать оба из-за сложности сохранения работы код; как указывалось ранее, это трудно сделать хорошо). В Python 2 это означает, что API, принимающие текст, могут работать с unicode, а те, которые работают с двоичными данными, работают с типом bytes из Python 3 (который является подмножеством str в Python 2 и действует как алиас для типа bytes в Python 2). Обычно самая большая проблема - это осознание того, какие методы существуют на каких типах в Python 2 и 3 одновременно (для текста, который unicode в Python 2 и str в Python 3, для двоичного, который str/bytes в Python 2 и bytes в Python 3). В следующей таблице перечислены уникальный методы для каждого типа данных в Python 2 & 3 (например, decode() метод используется для эквивалентного двоичного типа данных в Python 2 или 3, но не может быть использован для типа текстовых данных между Python 2 и 3, потому что str в Python 3 не имеет метода). Обратите внимание, что начиная с Python 3.5 __mod__ метод был добавлен к типу байтов.

Текстовые данные Двоичные данные
decode
encode  
format  
isdecimal  
isnumeric  

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

Следующий выпуск удостоверяется, что вы знаете, представляют ли опечатки строка в вашем код текст или двоичные данные. Вы должны добавить префикс b к любому литерал, который представляет двоичные данные. Для текста необходимо добавить префикс u к тексту литерал. (существует импорт __future__, чтобы заставить все неопределенные литералы быть юникодом, но использование показало, что это не так эффективно, как добавление префикса b или u ко всем литералам явно)

В рамках этой дихотомии вы также должны быть осторожны с открытием файлов. Если вы не работаете в Windows, есть вероятность, что вы не всегда удосужились добавить режим b при открытии двоичного файла (например, rb для двоичного чтения). В разделе Python 3 двоичные файлы и текстовые файлы явно различны и взаимно несовместимы; для получения дополнительной информации см. модуль io. Поэтому вы, должны принимают решение того, будет ли файл используемый для двойного доступа (позволяющий двоичные данные быть прочитанным и/или написанным) или текстовый доступ (позволяющий текстовые данные быть прочитанным и/или написанным). Вы также должны использовать io.open() для открытия файлов вместо встроенной функции open(), так как модуль io согласован с Python 2 до 3, в то время как встроенная функция open() не является (в Python 3 это на самом деле io.open()). Не беспокойтесь с устаревшей практикой использования codecs.open(), так как это необходимо только для сохранения совместимости с Python 2.5.

Конструкторы как str, так и bytes имеют различную семантику для одних и тех же аргументов между Python 2 и 3. Передача целого числа в bytes в Python 2 даст строка представление целого числа: bytes(3) == '3'. Но в Python 3 целочисленный аргумент bytes даст вам объект в байтах до указанного целого числа, заполненного нулевыми байтами: bytes(3) == b'\x00\x00\x00'. Аналогичное беспокойство необходимо при передаче байтового объекта str. В Python 2 вы просто получите байты объекта обратно: str(b'3') == b'3'. Но в Python 3 вы получаете представление строка объекта байтов: str(b'3') == "b'3'".

Наконец, индексация двоичных данных требует тщательной обработки (разрезание делает не, требуют любой специальной обработки). В Python 2, b'123'[1] == b'2' находясь в Python 3 b'123'[1] == 50. Поскольку двоичные данные являются просто коллекцией двоичных чисел, Python 3 возвращает целое значение для байта, на котором вы индексируете. Но в Python 2, потому что bytes == str, индексирование возвращает одноэлементный фрагмент байтов. Проект six имеет функцию с именем six.indexbytes(), которая возвращает целое число, как в Python 3: six.indexbytes(b'123', 1).

Подводя итоги:

  1. Определите, какой из API принимает текст, а какой двоичные данные
  2. Удостоверьтесь, что ваш код, который работает с текстом также, работает с unicode и код для работ двоичных данных с bytes в Python 2 (см. приведенную выше таблицу для того, какой методы вы не можете использовать для каждого типа),
  3. Пометьте все двоичные литералы префиксом b, текстовые литералы префиксом u
  4. Декодировать двоичные данные в текст как можно скорее, кодировать текст как двоичные данные как можно позже
  5. Откройте файлы с помощью io.open() и при необходимости укажите режим b. Будьте осторожны при индексировании в двоичные данные

Вместо определения версии используйте функцию обнаружения компонентов

Неизбежно у вас будет код, который должен выбрать, что делать на основе того, какая версия Python запущена. Лучший способ сделать это с функцией определения того, поддерживает ли версия Python, в которой вы работаете, то, что вам нужно. Если по какой-то причине это не работает, вы должны сделать проверку версии в отношении Python 2, а не Python 3. Чтобы объяснить это, давайте рассмотрим пример.

Давайте притворимся, что вам нужен доступ к особенности importlib, который доступен в стандартной библиотеке Python’s начиная с Python 3.3 и доступен для Python 2 через importlib2 на PyPI. У вас может возникнуть искушение записать код для доступа, например, к модулю importlib.abc, выполнив следующие действия:

import sys

if sys.version_info[0] == 3:
    from importlib import abc
else:
    from importlib2 import abc

Проблема с этим код то, что происходит, когда Python 4 выходит? было бы лучше рассматривать Python 2 как исключительный случай вместо Python 3 и предположить, что будущие версии Python будут более совместимы с Python 3, чем Python 2:

import sys

if sys.version_info[0] > 2:
    from importlib import abc
else:
    from importlib2 import abc

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

try:
    from importlib import abc
except ImportError:
    from importlib2 import abc

Предотвратите регрессы совместимости

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

Чтобы помочь с пребыванием совместимого, у любых новых модулей, которые вы создаете, должен быть, по крайней мере, следующий блок код наверху его:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

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

Вы также можете использовать проект Pylint и его флаг --py3k, чтобы привязать ваш код, чтобы получать предупреждения, когда ваш код начинает отклоняться от совместимости Python 3. Это также предотвращает необходимость регулярного запуска Modernize или Futurize над вашим код для отслеживания регрессий совместимости. Это действительно требует, чтобы вы только поддержали Python 2.7 и Python 3.4 или более новый, поскольку это - минимальная поддержка Python пилинта вариантов.

Проверьте, какие зависимости блокируют переход

После вы сделали ваш код совместимым с Python 3, вы должны начать говорить о том, были ли ваши зависимости также портированы. Проект caniusepython3 был создан, чтобы помочь определить, какие проекты прямо или косвенно блокируют поддержку Python 3. По адресу https://caniusepython3.com имеется как инструмент командной строки, так и веб-интерфейс.

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

Обновите файл setup.py, чтобы обозначить совместимость Python 3

Как только ваш код работает в разделе Python 3, вы должны обновить классификаторы в вашем setup.py, чтобы содержать Programming Language :: Python :: 3 и не указывать только поддержку Python 2. Это расскажет всем, кто пользуется вашим код, что вы поддерживаете Python 2 и 3. В идеале вы также хотите добавить классификаторы для каждой основной/младшей версии Python, которую вы теперь поддерживаете.

Для обеспечения совместимости используйте непрерывную интеграцию

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

Можно также использовать флаг -bb с Python 3 интерпретатор для инициирования исключения при сравнении байтов с строки или байтов с int (последний доступен, начиная с Python 3.5). По умолчанию отличающиеся от типов сравнения просто возвращают False, но если вы ошиблись в разделении обработки текста/двоичных данных или индексации на байтах, вы не сможете легко найти ошибку. Этот флаг вызовет исключение, когда происходят такого рода сравнения, что значительно облегчает поиск ошибки.

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

Рассмотрите возможность использования дополнительной проверки статического типа

Другой способ помочь портировать ваш код - использовать статическую проверку типа, такую как mypy или pytype на вашем код. Эти инструменты могут быть используемый для анализа вашего код, как если бы он выполняется под Python 2, то вы можете запустить инструмент второй раз, как если бы ваш код работает под Python 3. Выполнив статическую проверку типа дважды, вы можете обнаружить, что вы, например, неправильно используете двоичный тип данных в одной версии Python по сравнению с другой. Если вы добавляете, что дополнительный тип намекает вашему код, вы можете также явно состояние, используют ли ваши API текстовые или двоичные данные, помогая удостовериться, что все функционирует как ожидалось в обеих версиях Python.