Статьи

Создание кода, совместимого с Python 2 и 3

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

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

Чтобы внести свой вклад в эти усилия, я успешно преобразовал две основные кодовые базы для работы на Python 2.6 и 3.1:

  • pycparser — анализатор ANSI C на чистом Python: новая версия (1.07) может работать на обеих версиях Python (кроме этого, она не отличается от 1.06)
  • Luz — набор ассемблера / компоновщика / процессора также перенесен.

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

Вот список некоторых трюков, которые мне приходилось использовать, без определенного порядка. Прежде всего, я создал файл portability.py, который также максимально полно отражает различия. Иногда мне приходилось использовать следующую проверку:

if sys.hexversion > 0x03000000

Различать версии Python. К счастью, все такие проверки могут быть ограничены portability.py .

Вот пример пары функций из portability.py :

def printme(s):
    sys.stdout.write(str(s))

def get_input(prompt):
    if sys.hexversion > 0x03000000:
        return input(prompt)
    else:
        return raw_input(prompt)

Python 3 превратил печать в функцию, так что в качестве оператора он даже не анализируется. printme — это функция, которая может вызываться обеими версиями Python. Он не так универсален, как сама печать , но это небольшая проблема, поскольку я в основном использовал печать для отладки, тестирования и некоторого тривиального вывода.

get_input инкапсулирует отсутствие raw_input в Python 3.

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

except TypeError:
    err = sys.exc_info()[1]

Этот код работает в обеих версиях и помещает сообщение об исключении в err .

Некоторые различия были очень просты в обращении. Например, Python 3 удалил xrange , поэтому я только что использовал list (range . Если бы производительность действительно имела значение, мне пришлось бы использовать что-то более сложное. Кроме того, itertools.imap был удален, поэтому я заменил его на iter (map . их член has_key , но key в dict хорошо работает в обеих версиях Python, так что это еще одно простое изменение.

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

Возможности полного тестирования в Luz доставили мне некоторые неприятности, потому что я использую динамическую загрузку кода Python. Новый модуль исчез в Python 3, но , к счастью imp.new_module заменяет его и работает в 2.6 , а также. Кроме того, мне пришлось использовать трюк, заимствованный у Неда, чтобы заменить exec этим чудовищем:

# Borrowed from Ned Batchelder
if sys.hexversion > 0x03000000:
    def exec_function(source, filename, global_map):
        exec(compile(source, filename, "exec"), global_map)
else:
    eval(compile("""\
def exec_function(source, filename, global_map):
    exec compile(source, filename, "exec") in global_map
""",
    "<exec_function>", "exec"))

Точно так же, как перехват исключений, так как exec является синтаксисом , вы просто не можете его скрыть за проверкой версии. Парсер захлебывается им, даже если этот фрагмент кода не выполняется в конце концов. Следовательно, необходим метод грубой силы с использованием eval (compile ), поскольку он выполняется во время выполнения, когда его видит только соответствующий интерпретатор.

Вот и все. С сегодняшнего дня я планирую поддерживать работоспособность Pycparser и Luz в обеих версиях Python — это не должно быть слишком сложно. В будущем, когда я почувствую, что настало время переключиться на Py3k, это будет тривиально — я просто исправлю весь ужасный код переносимости.

PS: чтобы выполнить такую ​​задачу, вам действительно нужны хорошие юнит-тесты. Я не могу представить, как сделать это и остаться в здравом уме без обширных тестов, которые есть у обеих баз кода.

Похожие сообщения:

  1. Создание модулей расширения Python на C
  2. Использование кода под GPL для внутреннего программного обеспечения