Spec-Zone.ru › Python 3.10

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

author

Бретт Кэннон

Аннотация

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

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

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

Для получения помощи по переводу можно обратиться к архивному списку рассылки python-porting.

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

Для создания одноисточникового проекта, совместимого с Python 2/3, выполните следующие шаги:

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

Примечание

Примечание: использование python -m pip install гарантирует, что pip вызываемый вами — тот, который установлен для текущего Python, будь то системный pip или установленный в виртуальной среде.

Подробности

Ключевой момент в поддержке 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 (python -m pip install six). Однако помните, что почти все проекты, перечисленные в этом руководстве, вам не будут доступны.

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

Однако вы должны стремиться поддерживать только Python 2.7. Python 2.6 больше не поддерживается и не получает исправления ошибок. Это означает, что вам придется самостоятельно устранять любые проблемы, с которыми вы столкнетесь с Python 2.6. Также есть некоторые инструменты, упомянутые в этом руководстве, которые не поддерживают Python 2.6 (например, Pylint), и это станет более распространенной практикой по мере развития событий. Просто будет проще, если вы будете поддерживать только те версии Python, которые вам необходимы.

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

В вашем файле setup.py должен быть правильный классификатор trove, указывающий, какие версии 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, напрямую полагаясь на six для обеспечения совместимости. Поскольку Python 3 — это будущее, лучше рассмотреть Futurize, чтобы начать адаптироваться к новым практикам, которые Python 3 вводит и к которым вы ещё не привыкли.

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

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

Деление

В 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__ был добавлен к типу bytes.

Текстовые данные

Двоичные данные

decode

encode

format

isdecimal

isnumeric

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

Следующая проблема заключается в том, чтобы убедиться, что строковые литералы в вашем коде представляют текст или двоичные данные. Вы должны добавить префикс b к любому литералу, представляющему двоичные данные. Для текста вы должны добавить префикс u к текстовому литералу. (есть импорт __future__, чтобы принудительно сделать все не указанные литералы Unicode, но практика показала, что это не так эффективно, как добавление префикса 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 нужной длины, заполненный нулевыми байтами: bytes(3) == b'\x00\x00\x00'. Аналогичная забота необходима при передаче объекта bytes в str. В Python 2 вы просто получите объект bytes обратно: str(b'3') == b'3'. Но в Python 3 вы получите строковое представление объекта bytes: 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 при необходимости
  6. Будьте внимательны при индексировании двоичных данных

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

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

Предположим, что вам нужен доступ к функции модуля importlib, доступной в стандартной библиотеке Python начиная с 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 у Pylint.

END_OF_DOCUMENT_MARKER

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

После того, как вы адаптировали свой код к 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, чтобы вызывать исключение при сравнении байтов с строками или байтов с целым числом (последнее доступно начиная с Python 3.5). По умолчанию сравнения с разными типами просто возвращают False, но если вы допустили ошибку в разделении обработки текстовых/двоичных данных или индексирования байтов, вы бы не легко обнаружили ошибку. Этот флаг вызовет исключение при возникновении таких сравнений, что позволит проще отследить ошибку.

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

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

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

© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/howto/pyporting.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API