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

Перенос кода с Python 2 на Python 3Porting Python 2 Code to Python 3

автор:Brett Cannon

Аннотация

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

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

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

Если вы предпочитаете прочитать (бесплатную) книгу по переносу проекта на Python 3, обратите внимание на книгу Porting to Python 3 Леннарта Регебро; она охватывает большую часть материала, обсуждаемого в данном руководстве.

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

Краткая версияThe Short Version

  • Определите самую старую версию Python 2, которую планируется поддерживать (если таковая имеется)
  • Убедитесь, что имеется тщательный набор тестов, и используйте непрерывное интеграционное тестирование, чтобы гарантировать совместимость с нужными версиями Python
  • Если у вас есть зависимости, проверьте их статус Python 3 с помощью caniusepython3 (инструмент командной строки, веб-приложение)

После этого доступны следующие варианты:

  • Если вы отказываетесь от поддержки Python 2, используйте 2to3 для переноса на Python 3
  • If you are keeping Python 2 support, then start writing Python 2/3-compatible code starting TODAY
    • Если у вас есть не перенесённые зависимости, свяжитесь с их авторами для переноса их проекта, а тем временем делайте свой код совместимым с Python 3, чтобы быть готовым, когда все зависимости будут перенесены
    • Если все ваши зависимости перенесены (или у вас их нет), смело переносите на Python 3
  • Если вы создаёте новый проект, который должен быть совместим с 2/3, пишите код на Python 3, а затем портируйте обратно на Python 2

Перед началомBefore You Begin

Если ваш проект находится на Cheeseshop/PyPI, убедитесь, что у него есть правильные trove-классификаторы, чтобы указать, какие версии Python он в настоящее время поддерживает. Как минимум, вы должны указать основную версию (версии), например Programming Language :: Python :: 2, если ваш проект в настоящее время поддерживает только Python 2. Предпочтительно быть максимально конкретным, перечислив каждую основную/дополнительную версию Python, которую вы поддерживаете, например, если ваш проект поддерживает Python 2.6 и 2.7, то вам нужны классификаторы:

Programming Language :: Python :: 2
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7

Как только ваш проект будет поддерживать Python 3, вам нужно будет вернуться и добавить соответствующие классификаторы для Python 3. Это важно, так как установка классификатора Programming Language :: Python :: 3 приведёт к тому, что ваш проект будет указан в разделе Python 3 Packages на PyPI.

Убедитесь, что у вас есть надёжный набор тестов. Нужно гарантировать, что всё продолжает работать, как и при поддержке нового минорного/функционального релиза Python. Это означает, что ваш набор тестов должен быть тщательным и правильно перенесён между Python 2 и 3 (рассмотрите использование coverage для измерения эффективного покрытия тестами). Скорее всего, вам также захочется использовать что-то вроде tox для автоматизации тестирования на всех поддерживаемых версиях Python. Кроме того, сначала перенесите тесты, чтобы вовремя обнаружить поломки во время перехода. Тесты, как правило, проще, чем тестируемый код, поэтому они дают представление о том, насколько легко может быть перенос кода.

По возможности откажитесь от поддержки старых версий Python. Python 2.5 ввёл много полезного синтаксиса и библиотек, которые стали идиоматичными в Python 3. Python 2.6 ввёл future-инструкции, что значительно упрощает совместимость при переходе с Python 2 на 3. Python 2.7 продолжает эту тенденцию в стандартной библиотеке. Выберите самую новую версию Python, которую вы считаете минимально поддерживаемой, и работайте от неё.

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

Написание совместимого кода на Python 2 и 3Writing Source-Compatible Python 2/3 Code

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

Проекты, которые стоит рассмотретьProjects to Consider

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

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

Если вы хотите, чтобы ваш совместимый код выглядел более похожим на Python 3, существует проект future. Он пытается предоставить обратные переносы (backports) объектов из Python 3, чтобы вы могли использовать их в коде, совместимом с Python 2, например, заменить тип bytes из Python 2 на соответствующий тип из Python 3. Он также предоставляет скрипт перевода, подобный modernize (его код перевода частично основан на modernize), чтобы помочь начать работу с существующей кодовой базой. Он также уникален тем, что его скрипт перевода может переносить код Python 3 обратно, а код Python 2 – вперёд.

Советы и хитростиTips & Tricks

Чтобы упростить написание совместимого с исходным кодом кода с использованием одного из проектов, упомянутых в Projects to Consider, следуйте приведённым ниже рекомендациям. Некоторые из них уже обрабатываются рекомендуемыми проектами, поэтому если вы используете один из них, сначала прочитайте его документацию, чтобы узнать, какие из приведённых ниже советов будут выполнены автоматически.

Поддержка Python 2.7Support Python 2.7

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

Старайтесь поддерживать только Python 2.6 и новееTry to Support Python 2.6 and Newer Only

While not possible for all projects, if you can support Python 2.6 and newer only, your life will be much easier. Various future statements, stdlib additions, etc. exist only in Python 2.6 and later which greatly assist in supporting Python 3. But if you project must keep support for Python 2.5 then it is still possible to simultaneously support Python 3.

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

from __future__ import print_function

Это не только приучает писать print() как функцию вместо оператора, но и даёт все преимущества, которые функция имеет по сравнению с оператором Python 2 (six предоставляет функцию, если поддерживается Python 2.5 или старше).

from __future__ import unicode_literals

Если используется этот future-оператор, то все строковые литералы в Python 2 будут считаться Unicode (как уже происходит в Python 3). Если не использовать этот future-оператор, то следует помечать все текстовые строки префиксом u и поддерживать только Python 3.3 или новее. Но настоятельно рекомендуется сделать что-то одно (six предоставляет функцию, если не хочется использовать future-оператор и требуется поддержка Python 3.2 или старше).

Байтовые/строковые литералыBytes/string literals

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

Этот момент нельзя переоценить: убедитесь, что вы знаете, какими должны быть все ваши строковые литералы Python 2 в Python 3. Любой строковый литерал, который должен обрабатываться как байты, должен иметь префикс b. Любой строковый литерал, который должен быть Unicode/текстом в Python 2, должен либо иметь литерал u (поддерживается, но игнорируется в Python 3.3 и новее), либо в начале файла должно быть from __future__ import unicode_literals. Но ключевой момент – вы должны знать, как Python 3 будет обрабатывать каждый ваш строковый литерал, и помечать их соответствующим образом.

Существуют некоторые различия между байтовыми литералами в Python 2 и Python 3 из-за того, что в Python 2 тип bytes является просто псевдонимом str. Смотрите раздел Handle Common “Gotchas”, чтобы знать, чего остерегаться.

from __future__ import absolute_import

Более подробно обсуждается ниже, но вам следует использовать этот future-оператор, чтобы случайно не применить неявные относительные импорты.

Поддержка только Python 2.5 и новееSupporting Python 2.5 and Newer Only

Если вы поддерживаете Python 2.5 и новее, всё ещё есть некоторые возможности Python, которые можно использовать.

from __future__ import absolute_import

Неявные относительные импорты (например, импорт spam.bacon из spam.eggs с помощью оператора import bacon) не работают в Python 3. Этот future-оператор отходит от этого и позволяет использовать явные относительные импорты (например, from . import bacon).

В Python 2.5 необходимо использовать оператор __future__, чтобы иметь возможность использовать явные относительные импорты и предотвратить неявные. В Python 2.6 явные относительные импорты доступны и без этого оператора, но всё равно стоит использовать оператор __future__ для предотвращения неявных относительных импортов. В Python 2.7 оператор __future__ не нужен. Другими словами, если вы не поддерживаете только Python 2.7 или версию старше 2.5, используйте этот оператор __future__.

Помечайте все строки Unicode префиксом uMark all Unicode strings with a u prefix

Хотя в Python 2.6 есть оператор __future__, который автоматически заставляет Python 2 обрабатывать все строковые литералы как Unicode, в Python 2.5 такого сокращённого пути нет. Это означает, что вам нужно просмотреть все строковые литералы и пометить их префиксом u, чтобы явно превратить их в текстовые строки, где это необходимо, и поддерживать только Python 3.3 или новее. В противном случае используйте проект вроде six, который предоставляет функцию для обработки всех текстовых строковых литералов.

Перехват текущего возбуждённого исключенияCapturing the Currently Raised Exception

В Python 2.5 и ранее синтаксис для доступа к текущему исключению:

try:
  raise Exception()
except Exception, exc:
  # Текущее исключение: 'exc'.
  pass

Этот синтаксис изменился в Python 3 (и был перенесён в Python 2.6 и новее) на:

try:
  raise Exception()
except Exception as exc:
  # Текущее исключение: 'exc'.
  # В Python 3 'exc' ограничено блоком; в Python 2.6/2.7 оно "утекает".
  pass

Из-за этого изменения синтаксиса необходимо изменить способ перехвата текущего исключения в Python 2.5 и ранее на:

try:
  raise Exception()
except Exception:
  import sys
  exc = sys.exc_info()[1]
  # Текущее исключение: 'exc'.
  pass

Из sys.exc_info() можно получить больше информации о возбуждённом исключении, чем просто экземпляр текущего исключения, но, скорее всего, она не нужна.

Примечание

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

В Python 2 эта проблема возникает только в том случае, если сохранить саму трассировку (например, третий элемент кортежа, возвращаемого sys.exc_info()) в переменной.

Работа с распространёнными «подводными камнями»Handle Common “Gotchas”

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

from __future__ import division

Хотя того же результата можно добиться, используя аргумент -Qnew Python, использование этого future-оператора снимает требование, чтобы пользователи использовали этот флаг для получения ожидаемого поведения деления в Python 3 (например, 1/2 == 0.5; 1//2 == 0).

Указывайте при открытии файла в двоичном режимеSpecify when opening a file as binary

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

Текстовые файлыText files

Текстовые файлы, созданные с помощью open() в Python 2, возвращают байтовые строки, тогда как в Python 3 они возвращают строки Unicode. В зависимости от стратегии переноса это может быть проблемой.

Если требуется, чтобы текстовые файлы возвращали строки Unicode в Python 2, есть две возможности:

  • В Python 2.6 и выше используйте io.open(). Поскольку io.open() по сути та же функция и в Python 2, и в Python 3, это поможет устранить любые возможные проблемы.
  • Если требуется совместимость с версиями ниже 2.6, тогда используйте codecs.open(). Это гарантирует, что в Python 2 вы получите строки Unicode.
Наследуйте от objectSubclass object

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

Работа с дихотомией байтов и строкDeal With the Bytes/String Dichotomy

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

Разметка строковых литералов Python 2Mark Up Python 2 String Literals

Первое, что нужно сделать – обозначить каждый строковый литерал в Python 2 как текстовый или байтовый. Если вы поддерживаете только Python 2.6 или новее, это можно сделать, пометив байтовые литералы префиксом b, а текстовые – префиксом u или с помощью future-оператора unicode_literals.

Если ваш проект поддерживает версии Python до 2.6, тогда следует использовать проект six и его функцию b() для обозначения байтовых литералов. Для текстовых литералов можно использовать функцию u() из six или префикс u.

Определите, что будут принимать APIDecide what APIs Will Accept

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

Возьмем словарь {b'a': 'bytes', u'a': 'text'} в Python 2.6. Он создает словарь {u'a': 'text'}, так как b'a' == u'a'. Но в Python 3 эквивалентный словарь создает {b'a': 'bytes', 'a': 'text'}, т.е. без потери данных. Подобные проблемы могут возникать при переносе кода Python 2 на Python 3.

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

Сравнение байтов и UnicodeBytes / Unicode Comparison

В Python 3 смешивание байтов и unicode запрещено в большинстве ситуаций; это вызовет TypeError, тогда как Python 2 попытался бы выполнить неявное приведение типов. Однако есть один случай, когда это не так, и это может ввести в заблуждение:

>>> b"" == ""
False

Это потому, что сравнение на равенство по языку всегда должно завершаться успехом (и возвращать False для несовместимых типов). Однако это также означает, что неправильно портированный код на Python 3 может демонстрировать ошибочное поведение, если такие сравнения выполняются незаметно. Для обнаружения таких ситуаций в Python 3 есть флаг -b, который будет показывать предупреждение:

$ python3 -b
>>> b"" == ""
__main__:1: BytesWarning: Comparison between bytes and string
False

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

$ python3 -bb
>>> b"" == ""
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
BytesWarning: Comparison between bytes and string
Индексирование байтовых объектовIndexing bytes objects

Еще одно потенциально удивительное изменение – это поведение индексирования байтовых объектов в Python 3:

>>> b"xyz"[0]
120

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

>>> b"xyz"[0:1]
b'x'
>>> n = 1
>>> b"xyz"[n:n+1]
b'y'

Единственная оставшаяся ловушка в том, что выход за границы при срезе возвращает пустой байтовый объект вместо возбуждения IndexError:

>>> b"xyz"[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index out of range
>>> b"xyz"[3:4]
b''
__str__()/__unicode__()

В Python 2 объекты могут определять как строковое, так и unicode представление себя. В Python 3, однако, существует только строковое представление. Это становится проблемой, поскольку люди могут нечаянно делать в своих методах __str__() вещи, которые приводят к непредсказуемым результатам (например, бесконечная рекурсия, если использовать идиому unicode(self).encode('utf8') в качестве тела метода __str__()).

Можно использовать mixin-класс для обхода этого. Это позволяет определить только метод __unicode__() для вашего класса, а mixin выведет __str__() за вас (код из http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/):

import sys

class UnicodeMixin(object):

  """Миксин-класс для определения правильных методов __str__/__unicode__
  методов в Python 2 или 3.

  if sys.version_info[0] >= 3: # Python 3
      def __str__(self):
          return self.__unicode__()
  else:  # Python 2
      def __str__(self):
          return self.__unicode__().encode('utf8')


class Spam(UnicodeMixin):

  def __unicode__(self):
      return u'spam-spam-bacon-spam'  # 2to3 удалит префикс 'u'.
Не индексируйте исключенияDon’t Index on Exceptions

В Python 2 следующее работало:

>>> exc = Exception(1, 2, 3)
>>> exc.args[1]
2
>>> exc[1]  # Только Python 2!
2

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

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

Не используйте __getslice__ и компаниюDon’t use __getslice__ & Friends

Уже некоторое время считалось устаревшим, но Python 3 наконец отказался от поддержки __getslice__() и т.д. Полностью переходите на __getitem__() и ее друзей.

Обновление doctest-овUpdating doctests

Не забывайте также делать их совместимыми с Python 2/3. Если вы написали монолитный набор доктестов (например, одну строку документации, содержащую все доктесты), вам стоит хотя бы подумать о разбиении доктестов на более мелкие части, чтобы их было проще исправлять. В противном случае, возможно, стоит потратить время и усилия на перенос тестов в unittest.

Обновление map для несбалансированных входных последовательностейUpdate map for imbalanced input sequences

В Python 2, когда map передавалось более одной входной последовательности, он дополнял более короткие последовательности значениями None, возвращая последовательность такой же длины, как самая длинная входная последовательность.

В Python 3, если входные последовательности для map имеют разную длину, map остановится по завершении самой короткой из последовательностей. Для полной совместимости с map из Python 2.x оберните аргументы последовательностей в itertools.zip_longest(), например map(func, *sequences) становится list(map(func, itertools.zip_longest(*sequences))).

Устранение -3 предупрежденийEliminate -3 Warnings

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

Альтернативные подходыAlternative Approaches

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

Поддержка только Python 3 в будущем из кода на Python 2Supporting Only Python 3 Going Forward From Python 2 Code

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

Перенос кода с Python 3 на Python 2Backporting Python 3 code to Python 2

Если у вас есть код на Python 3 и вы не заинтересованы в поддержке Python 2, вы можете использовать 3to2 для перевода с Python 3 на Python 2. Это рекомендуется только в том случае, если вы не планируете активно поддерживать пользователей Python 2. В противном случае пишите свой код для Python 3, а затем переносите его обратно настолько далеко, насколько хотите. Обычно это проще, чем переход с Python 2 на 3, поскольку вы уже решите все трудности, например, с байтами/строками и т.д.

Другие ресурсыOther Resources

Авторы следующих постов в блогах, страниц вики и книг заслуживают особой благодарности за публикацию своих советов по переносу кода с Python 2 на Python 3 (и, таким образом, за помощь в предоставлении информации для этого документа и его различных редакций на протяжении многих лет):

Если вы считаете, что в этом документе чего-то не хватает и это следует добавить, отправьте письмо в список рассылки python-porting.