Документация Python неофициальный перевод

pyporting.md

188 строк · 37.0 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.8/howto/pyporting.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Перенос кода с Python 2 на Python 389**автор**1011Brett Cannon1213Аннотация1415Поскольку Python 3 – это будущее Python, а Python 2 всё ещё активно используется, полезно иметь проект, доступный для обеих основных версий Python. Это руководство призвано помочь разобраться, как наилучшим образом поддерживать обе версии Python 2 и 3 одновременно.1617Если требуется портировать модуль расширения, а не чистый код Python, см. [Портирование модулей расширения на Python 3](https://python-all.ru/3.8/howto/cporting.html#cporting-howto).1819Если вы хотите узнать мнение одного из основных разработчиков Python о том, почему Python 3 появился, вы можете прочитать [Python 3 Q & A](https://python-all.ru/3.8/howto/pyporting.html) Ника Коглена или [Why Python 3 exists](https://python-all.ru/3.8/howto/pyporting.html) Бретта Кэннона.2021Для помощи с портированием можно написать в список рассылки [python-porting](https://python-all.ru/3.8/howto/pyporting.html) с вопросами.2223## Краткое объяснение2425Чтобы сделать ваш проект совместимым с Python 2 и 3 из единой исходной базы, основные шаги следующие:26271. Сосредоточьтесь на поддержке только Python 2.7282. Убедитесь, что у вас хорошее тестовое покрытие ([coverage.py](https://python-all.ru/3.8/howto/pyporting.html) может помочь; `pip install coverage`)293. Изучите различия между Python 2 и 3304. Используйте [Futurize](https://python-all.ru/3.8/howto/pyporting.html) (или [Modernize](https://python-all.ru/3.8/howto/pyporting.html)) для обновления кода (например, `pip install future`)315. Используйте [Pylint](https://python-all.ru/3.8/howto/pyporting.html), чтобы убедиться, что вы не ухудшаете поддержку Python 3 (`pip install pylint`)326. Используйте [caniusepython3](https://python-all.ru/3.8/howto/pyporting.html), чтобы узнать, какие из ваших зависимостей блокируют использование Python 3 (`pip install caniusepython3`)337. Когда ваши зависимости больше не блокируют вас, используйте непрерывную интеграцию, чтобы убедиться, что вы остаётесь совместимы с Python 2 и 3 ([tox](https://python-all.ru/3.8/howto/pyporting.html) может помочь в тестировании с несколькими версиями Python; `pip install tox`)348. Рекомендуется рассмотреть использование опциональной статической проверки типов, чтобы убедиться, что типы работают в обеих версиях Python 2 и 3 (например, используйте [mypy](https://python-all.ru/3.8/howto/pyporting.html) для проверки типов в обеих версиях Python 2 и Python 3).3536## Подробности3738Ключевой момент в поддержке Python 2 и 3 одновременно заключается в том, что вы можете начать **сегодня**! Даже если ваши зависимости ещё не поддерживают Python 3, это не значит, что вы не можете модернизировать свой код **сейчас** для поддержки Python 3. Большинство изменений, необходимых для поддержки Python 3, приводят к более чистому коду с использованием новых практик даже в коде Python 2.3940Ещё один ключевой момент: модернизация вашего кода Python 2 для поддержки Python 3 в значительной степени автоматизирована. Хотя, возможно, вам придётся принять некоторые решения по API из-за того, что Python 3 разграничивает текстовые и двоичные данные, но низкоуровневая работа теперь в основном сделана за вас, и поэтому вы можете сразу же воспользоваться преимуществами автоматизированных изменений.4142Помните эти ключевые моменты, пока читаете дальше о деталях переноса вашего кода для поддержки Python 2 и 3 одновременно.4344### Прекратите поддержку Python 2.6 и старше4546Хотя вы можете заставить Python 2.5 работать с Python 3, это **гораздо** проще, если вам нужно работать только с Python 2.7. Если отказ от Python 2.5 не является вариантом, то проект [six](https://python-all.ru/3.8/howto/pyporting.html) может помочь вам одновременно поддерживать Python 2.5 и 3 (`pip install six`). Однако имейте в виду, что почти все проекты, перечисленные в этом HOWTO, будут вам недоступны.4748Если вы можете пропустить Python 2.5 и старше, то необходимые изменения в вашем коде должны по-прежнему выглядеть и ощущаться как идиоматичный код Python. В худшем случае вам придётся использовать функцию вместо метода в некоторых случаях или импортировать функцию вместо использования встроенной, но в остальном общая трансформация не должна казаться вам чуждой.4950Но вам следует стремиться к поддержке только Python 2.7. Python 2.6 больше не получает свободной поддержки и, следовательно, не получает исправлений ошибок. Это означает, что **вам** придётся обходить любые проблемы, с которыми вы столкнётесь в Python 2.6. Есть также некоторые инструменты, упомянутые в этом HOWTO, которые не поддерживают Python 2.6 (например, [Pylint](https://python-all.ru/3.8/howto/pyporting.html)), и со временем это станет более распространённым. Для вас будет просто проще, если вы будете поддерживать только те версии Python, которые должны поддерживать.5152### Убедитесь, что вы указали правильную поддержку версий в вашем `setup.py` файле5354В вашем файле `setup.py` должен быть указан правильный [trove-классификатор](https://python-all.ru/3.8/howto/pyporting.html), указывающий, какие версии Python вы поддерживаете. Поскольку ваш проект пока не поддерживает Python 3, вы должны как минимум указать `Programming Language :: Python :: 2 :: Only`. В идеале вам следует также указать каждую основную/минорную версию Python, которую вы поддерживаете, например, `Programming Language :: Python :: 2.7`.5556### Обеспечьте хорошее тестовое покрытие5758Когда ваш код будет поддерживать самую старую версию Python 2, которую вы хотите, вам нужно убедиться, что ваш тестовый набор имеет хорошее покрытие. Хорошее эмпирическое правило заключается в том, что если вы хотите быть достаточно уверены в своём тестовом наборе, чтобы любые сбои, появляющиеся после переработки кода инструментами, были реальными ошибками в инструментах, а не в вашем коде. Если вы хотите стремиться к числу, попробуйте достичь более 80% покрытия (и не расстраивайтесь, если вам трудно добиться более 90% покрытия). Если у вас ещё нет инструмента для измерения тестового покрытия, то [coverage.py](https://python-all.ru/3.8/howto/pyporting.html) рекомендуется.5960### Изучите различия между Python 2 и 36162Когда ваш код будет хорошо протестирован, вы будете готовы начать перенос кода на Python 3! Но чтобы полностью понять, как изменится ваш код и на что следует обращать внимание во время написания кода, вам нужно узнать, какие изменения вносит Python 3 по сравнению с Python 2. Обычно два лучших способа сделать это – прочитать документацию [«What’s New»](https://python-all.ru/3.8/whatsnew/index.html#whatsnew-index) для каждого выпуска Python 3 и книгу [«Porting to Python 3»](https://python-all.ru/3.8/howto/pyporting.html) (которая доступна бесплатно онлайн). Также есть удобная [шпаргалка](https://python-all.ru/3.8/howto/pyporting.html) от проекта Python-Future.6364### Обновите свой код6566Когда станет понятно, чем Python 3 отличается от Python 2, пора обновлять код. Есть два инструмента для автоматического переноса кода: [Futurize](https://python-all.ru/3.8/howto/pyporting.html) и [Modernize](https://python-all.ru/3.8/howto/pyporting.html). Выбор инструмента зависит от того, насколько код должен быть похож на Python 3. [Futurize](https://python-all.ru/3.8/howto/pyporting.html) старается привнести идиомы и практики Python 3 в Python 2, например переносит тип `bytes` из Python 3, чтобы обеспечить семантическую совместимость между основными версиями Python. [Modernize](https://python-all.ru/3.8/howto/pyporting.html), с другой стороны, более консервативен и ориентирован на подмножество Python, совместимое с обеими версиями, полагаясь на [six](https://python-all.ru/3.8/howto/pyporting.html) для обеспечения совместимости. Поскольку будущее за Python 3, возможно, стоит рассмотреть Futurize, чтобы начать привыкать к новым подходам, которые вводит Python 3.6768Независимо от выбранного инструмента, он обновит код для работы под Python 3, сохраняя совместимость с исходной версией Python 2. В зависимости от желаемой степени консервативности можно сначала прогнать инструмент по набору тестов и визуально проверить diff, чтобы убедиться в корректности преобразования. После преобразования тестов и подтверждения, что все тесты по-прежнему проходят, можно преобразовывать код приложения, зная, что любой упавший тест – это ошибка перевода.6970К сожалению, инструменты не могут автоматизировать всё, чтобы код работал под Python 3, поэтому некоторые вещи придётся исправлять вручную для полной поддержки Python 3 (какие именно шаги необходимы, зависит от инструмента). Прочтите документацию выбранного инструмента, чтобы узнать, что он исправляет по умолчанию и что может сделать опционально, чтобы понять, что будет (не) исправлено автоматически, а что нужно править самостоятельно (например, использование `io.open()` вместо встроенной функции `open()` отключено по умолчанию в Modernize). К счастью, есть лишь несколько моментов, которые могут вызвать серьёзные проблемы и которые трудно отладить, если их не отслеживать.7172#### Деление7374В Python 3 `5 / 2 == 2.5`, а не `2`; любое деление между `int` значениями даёт `float`. Это изменение было запланировано ещё с Python 2.2, выпущенного в 2002 году. С тех пор пользователям рекомендовалось добавлять `from __future__ import division` во все файлы, использующие операторы `/` и `//`, или запускать интерпретатор с флагом `-Q`. Если вы этого не делали, придётся пройтись по коду и сделать две вещи:75761. Добавить `from __future__ import division` в файлы772. Обновить все операторы деления по необходимости: либо использовать `//` для целочисленного деления, либо продолжать использовать `/` и ожидать число с плавающей запятой7879Причина, по которой `/` не преобразуется просто в `//` автоматически, заключается в том, что если объект определяет метод `__truediv__`, но не `__floordiv__`, то код начнёт давать сбои (например, пользовательский класс, использующий `/` для обозначения некоторой операции, но не `//` для того же самого или вообще).8081#### Текстовые и бинарные данные8283В Python 2 можно было использовать тип `str` как для текста, так и для бинарных данных. К сожалению, такое смешение двух разных понятий приводило к хрупкому коду, который иногда работал с любым типом данных, а иногда нет. Это также могло приводить к запутанным API, если разработчики явно не указывали, что нечто, принимающее `str`, принимает либо текст, либо бинарные данные, а не один конкретный тип. Это усложняло ситуацию, особенно для тех, кто поддерживал несколько языков, поскольку API не утруждали себя явной поддержкой `unicode`, заявляя о поддержке текстовых данных.8485Чтобы сделать различие между текстовыми и бинарными данными более чётким и заметным, Python 3 поступил так, как большинство языков, созданных в эпоху интернета: сделал текстовые и бинарные данные различными типами, которые нельзя слепо смешивать (Python появился до широкого распространения интернета). Для кода, работающего только с текстом или только с бинарными данными, такое разделение не представляет проблемы. Но для кода, который должен работать и с тем, и с другим, теперь придётся учитывать, когда используются текстовые, а когда бинарные данные, поэтому полностью автоматизировать это невозможно.8687Для начала нужно решить, какие API принимают текст, а какие – бинарные данные (**настоятельно** рекомендуется не проектировать API, способные принимать и то, и другое, из-за сложности поддержки работоспособности кода; как уже говорилось, сделать это качественно трудно). В Python 2 это означает, что API, работающие с текстом, должны быть совместимы с `unicode`, а API для бинарных данных – с типом `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, так и в Python 3, но не может использоваться для текстового типа последовательно в обеих версиях, потому что в Python 3 у `str` нет этого метода). Обратите внимание: начиная с Python 3.5 у типа bytes появился метод `__mod__`.8889| **Текстовые данные** | **Бинарные данные** |90| --- | --- |91|  | decode |92| encode |  |93| формат |  |94| isdecimal |  |95| isnumeric |  |9697Упростить различение можно с помощью кодирования и декодирования между бинарными данными и текстом на границах кода. Это означает, что при получении текста в бинарном виде его следует сразу декодировать. А если коду нужно отправить текст в бинарном виде, то кодировать его как можно позже. Это позволяет коду работать внутри только с текстом и избавляет от необходимости отслеживать, с каким типом данных ведётся работа.9899Следующий вопрос – убедиться, что строковые литералы в коде представляют собой текст или бинарные данные. К любому литералу, представляющему бинарные данные, следует добавить префикс `b`. Для текста к текстовому литералу следует добавить префикс `u` (существует импорт [`__future__`](https://python-all.ru/3.8/library/__future__.html#module-__future__), который принудительно делает все неуточнённые литералы Unicode, но практика показала, что это не так эффективно, как явное добавление префикса `b` или `u` ко всем литералам).100101В рамках этой дихотомии также нужно быть внимательным при открытии файлов. Если вы не работали в Windows, возможно, вы не всегда утруждали себя добавлением режима `b` при открытии бинарного файла (например, `rb` для чтения в бинарном режиме). В Python 3 бинарные и текстовые файлы чётко различаются и взаимно несовместимы; подробнее см. модуль [`io`](https://python-all.ru/3.8/library/io.html#module-io). Поэтому **необходимо** принять решение, будет ли файл использоваться для бинарного доступа (чтение и/или запись бинарных данных) или текстового доступа (чтение и/или запись текстовых данных). Также следует использовать [`io.open()`](https://python-all.ru/3.8/library/io.html#io.open) для открытия файлов вместо встроенной функции [`open()`](https://python-all.ru/3.8/library/functions.html#open), поскольку модуль [`io`](https://python-all.ru/3.8/library/io.html#module-io) единообразен в Python 2 и 3, а встроенная функция [`open()`](https://python-all.ru/3.8/library/functions.html#open) – нет (в Python 3 это на самом деле [`io.open()`](https://python-all.ru/3.8/library/io.html#io.open)). Не стоит заморачиваться с устаревшей практикой использования [`codecs.open()`](https://python-all.ru/3.8/library/codecs.html#codecs.open) – это необходимо только для совместимости с Python 2.5.102103Конструкторы `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'"`.104105Наконец, индексация бинарных данных требует осторожного обращения (срезы **не** требуют особого обращения). В Python 2 `b'123'[1] == b'2'`, тогда как в Python 3 `b'123'[1] == 50`. Поскольку бинарные данные – это просто набор бинарных чисел, Python 3 возвращает целое значение байта по индексу. Но в Python 2 из-за `bytes == str` индексация возвращает срез из одного байта. В проекте [six](https://python-all.ru/3.8/howto/pyporting.html) есть функция `six.indexbytes()`, которая возвращает целое число, как в Python 3: `six.indexbytes(b'123', 1)`.106107Подводя итог:1081091. Определить, какие API принимают текст, а какие – бинарные данные1102. Убедиться, что код для работы с текстом совместим с `unicode`, а код для бинарных данных – с `bytes` в Python 2 (см. таблицу выше, какие методы нельзя использовать для каждого типа)1113. Пометить все бинарные литералы префиксом `b`, текстовые – префиксом `u`1124. Декодировать бинарные данные в текст как можно раньше, кодировать текст в бинарные данные как можно позже1135. Открывать файлы через [`io.open()`](https://python-all.ru/3.8/library/io.html#io.open) и указывать режим `b` там, где это необходимо1146. Быть осторожным при индексации бинарных данных115116#### Использовать проверку возможностей вместо проверки версии117118Неизбежно появится код, который должен выбирать действия в зависимости от версии Python. Лучший способ – проверять наличие необходимых возможностей в текущей версии Python. Если по какой-то причине это не срабатывает, следует проверять версию по Python 2, а не по Python 3. Чтобы пояснить это, рассмотрим пример.119120Давайте представим, что вам нужен доступ к возможности [`importlib`](https://python-all.ru/3.8/library/importlib.html#module-importlib), которая доступна в стандартной библиотеке Python начиная с Python 3.3 и доступна для Python 2 через [importlib2](https://python-all.ru/3.8/howto/pyporting.html) на PyPI. Возможно, вам захочется написать код для доступа, например, к модулю [`importlib.abc`](https://python-all.ru/3.8/library/importlib.html#module-importlib.abc), выполнив следующие действия:121122```python123import sys124125if sys.version_info[0] == 3:126    from importlib import abc127else:128    from importlib2 import abc129```130131Проблема этого кода в том, что произойдёт, когда выйдет Python 4? Было бы лучше рассматривать Python 2 как исключительный случай, а не Python 3, и предполагать, что будущие версии Python будут более совместимы с Python 3, чем с Python 2:132133```python134import sys135136if sys.version_info[0] > 2:137    from importlib import abc138else:139    from importlib2 import abc140```141142Однако лучшее решение – вообще не определять версию, а вместо этого полагаться на обнаружение возможностей. Это позволяет избежать потенциальных проблем с неправильным определением версии и помогает сохранять совместимость с будущими версиями:143144```python145try:146    from importlib import abc147except ImportError:148    from importlib2 import abc149```150151### Предотвращайте регрессии совместимости152153После того как вы полностью перевели свой код для совместимости с Python 3, вы захотите убедиться, что код не регрессирует и не перестаёт работать под Python 3. Это особенно важно, если у вас есть зависимость, которая в данный момент мешает вам запускать код на Python 3.154155Чтобы помочь с сохранением совместимости, любые новые модули, которые вы создаёте, должны содержать как минимум следующий блок кода в начале:156157```python158from __future__ import absolute_import159from __future__ import division160from __future__ import print_function161```162163Вы также можете запускать Python 2 с флагом `-3`, чтобы получать предупреждения о различных проблемах совместимости, которые ваш код вызывает во время выполнения. Если вы превратите предупреждения в ошибки с помощью `-Werror`, то сможете убедиться, что случайно не пропустили предупреждение.164165Вы также можете использовать проект [Pylint](https://python-all.ru/3.8/howto/pyporting.html) и его флаг `--py3k` для статического анализа вашего кода, чтобы получать предупреждения, когда ваш код начинает отклоняться от совместимости с Python 3. Это также избавит вас от необходимости регулярно прогонять [Modernize](https://python-all.ru/3.8/howto/pyporting.html) или [Futurize](https://python-all.ru/3.8/howto/pyporting.html) над вашим кодом, чтобы выявлять регрессии совместимости. Однако это требует, чтобы вы поддерживали только Python 2.7 и Python 3.4 или новее, так как это минимальная поддерживаемая версия Python для Pylint.166167### Проверьте, какие зависимости блокируют ваш переход168169**После того как** вы сделали свой код совместимым с Python 3, следует начать заботиться о том, были ли перенесены ваши зависимости. Проект [caniusepython3](https://python-all.ru/3.8/howto/pyporting.html) был создан, чтобы помочь вам определить, какие проекты – прямо или косвенно – блокируют вашу поддержку Python 3. Существует как инструмент командной строки, так и веб-интерфейс по адресу [https://caniusepython3.com](https://python-all.ru/3.8/howto/pyporting.html).170171Проект также предоставляет код, который вы можете интегрировать в свой набор тестов, чтобы у вас был падающий тест, когда у вас больше нет зависимостей, блокирующих использование Python 3. Это позволяет избежать ручной проверки зависимостей и быстро получать уведомление, когда можно начинать запускать код на Python 3.172173### Обновите ваш файл `setup.py`, чтобы указать совместимость с Python 3174175После того как ваш код будет работать под Python 3, вам следует обновить классификаторы в `setup.py`, чтобы они содержали `Programming Language :: Python :: 3` и не указывали только поддержку Python 2. Это сообщит всем, кто использует ваш код, что вы поддерживаете Python 2 **и** 3. В идеале вам также стоит добавить классификаторы для каждой основной/дополнительной версии Python, которую вы теперь поддерживаете.176177### Используйте непрерывную интеграцию для сохранения совместимости178179Когда вы сможете полностью запускать код на Python 3, вы захотите убедиться, что ваш код всегда работает как на Python 2, так и на Python 3. Вероятно, лучший инструмент для запуска тестов в нескольких интерпретаторах Python – это [tox](https://python-all.ru/3.8/howto/pyporting.html). Затем вы можете интегрировать tox с вашей системой непрерывной интеграции, чтобы случайно не нарушить поддержку Python 2 или 3.180181Возможно, вы также захотите использовать флаг `-bb` с интерпретатором Python 3, чтобы вызывать исключение при сравнении байтов со строками или байтов с целым числом (последнее доступно начиная с Python 3.5). По умолчанию сравнения разных типов просто возвращают `False`, но если вы ошиблись в разделении обработки текстовых/двоичных данных или в индексации по байтам, вы не сможете легко найти ошибку. Этот флаг будет вызывать исключение при таких сравнениях, что значительно упрощает поиск ошибки.182183И это в основном всё! На данный момент ваша кодовая база совместима одновременно и с Python 2, и с Python 3. Ваше тестирование также будет настроено так, что вы случайно не нарушите совместимость с Python 2 или 3, независимо от того, какую версию вы обычно используете для запуска тестов во время разработки.184185### Рассмотрите возможность использования необязательной статической проверки типов186187Ещё один способ помочь с портированием кода – использовать статический анализатор типов, например [mypy](https://python-all.ru/3.8/howto/pyporting.html) или [pytype](https://python-all.ru/3.8/howto/pyporting.html). Эти инструменты можно использовать для анализа кода так, как если бы он выполнялся под Python 2, а затем запустить инструмент второй раз, как если бы код выполнялся под Python 3. Запуская статический анализатор типов дважды таким образом, вы можете обнаружить, например, неправильное использование двоичного типа данных в одной версии Python по сравнению с другой. Если вы добавите необязательные аннотации типов в свой код, вы также сможете явно указать, используют ли ваши API текстовые или двоичные данные, что поможет убедиться, что всё работает как ожидается в обеих версиях Python.188