Статьи

Python 3 Портирование Fun Redux


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

Один из больших уроков, которые я пытаюсь принять, — это поддержка Python 3 в чистом коде Python с единой кодовой базой. В частности, я пытаюсь избежать использования
2to3насколько это возможно. Хотя я думаю, что 2to3 является отличным инструментом, который может облегчить начало поддержки как Python 2, так и Python 3 из одной ветви кода, у него есть некоторые недостатки. Самая большая проблема с 2to3 в том, что он медленный; перебор кода Python может занять много времени, что может стать серьезным препятствием для вашей скорости разработки. Другая проблема 2to3 заключается в том, что он не всегда хорошо работает с другими инструментами разработки, такими как
python setup.py test и
virtualenv , и вам иногда приходится писать дополнительные специальные исправления для преобразования, которые 2to3 не обрабатывает.

Первоначально Автор Барри Варшавы

Учитывая, что почти весь код, который я пишу в эти дни, нацелен на Python 2.6 как минимально поддерживаемую версию Python 2, 2to3 может просто не понадобиться. С моим
портом dbus-python на Python 3 и с моими собственными
пакетами
flufl я экспериментирую с игнорированием 2to3 и пытаюсь написать одну кодовую базу для всех Python 2.6, 2.7 и 3.2. Мой коллега
Майкл Фурд очень преуспел в этом подходе, вплоть до Python 2.4, поэтому как минимум 2.6 не должно быть проблемой! Расширения C довольно просты, потому что у вас есть препроцессор C, чтобы помочь вам. Но оказывается, что это обычно не так уж сложно в чистом Python. Я сделал это в моем последнем выпуске
flufl.bounceпакет, и намереваюсь устранить 2to3 в других моих пакетах flufl также скоро.

Первое, что я сделал, это добавил
print_function к
импорту
__future__ во всех моих модулях. Раньше я только импортировал
unicode_literals и
absolute_import . Но в документах обычно используется много
операторов
print , поэтому переключение на
функцию
print () явно удаляет одно большое преобразование 2to3. Кроме того , чтобы разучиться десятилетие
печати мышечной памяти заявления
для
печати () функция на самом деле довольно хорошая. Таким образом, мой шаблон модуля теперь выглядит следующим образом (без комментария об авторских правах):

    from __future__ import absolute_import, print_function, unicode_literals

    __metaclass__ = type
    __all__ = [
        ]

Говоря о документах, вы действительно хотите, чтобы они имели тот же набор будущих импортов, что и весь ваш другой код. Я расскажу подробнее о том, как мои собственные пакеты настраивают doctest, но сейчас полезно знать, что я создаю
doctest.DocFileSuite для каждого doctest в моем пакете. Все эти комплекты имеют функцию
setup (), и среда тестирования Python будет вызывать их в соответствующее время, передавая
параметр
testobj . Этот аргумент имеет
атрибут
globs, который служит глобалом модуля для doctest. Все, что вам нужно сделать для включения будущего импорта в ваших документах, это сделать что-то вроде этого:

def setup(testobj):
    try:
        testobj.globs['absolute_import'] = absolute_import
        testobj.globs['print_function'] = print_function
        testobj.globs['unicode_literals'] = unicode_literals
    except NameError:
        pass

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

В последнем выпуске flufl.bounce я изменил API, чтобы все обнаруженные адреса электронной почты были явно байтовыми объектами в Python 3 (и 8-битных строках в Python 2). Это вызвало некоторые проблемы с моими doctests, потому что repr объектов байтов Python 3 отличается от repr 8-битных строк в Python 2. Когда вы печатаете объект в Python 2, вы получаете только содержимое строки, но когда вы распечатав их в Python 3, вы получите
префикс
b » .

% python
Python 2.7.2+ (default, Dec 18 2011, 17:30:39)
[GCC 4.6.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print b'foo'
foo
>>>
% python3
Python 3.2.2+ (default, Dec 19 2011, 12:03:32)
[GCC 4.6.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print(b'foo')
b'foo'

Это означает, что ваш doctest не может быть написан так, чтобы легко поддерживать обе версии Python, когда используются байты / 8-битные строки. Я использую следующий помощник, чтобы обойти это:

    def print_bytes(obj):
        if bytes is not str:
            obj = repr(obj)[2:-1]
        print(obj)

Помните, что в Python 2
байты являются просто псевдонимом для
str, поэтому этот код вызывается только в Python 3.

Еще одна забавная проблема с байтами / 8-битными строками заключается в том, что в Python 3 байтовые объекты не
имеют метода .format () . Так что, если вы делаете что-то вроде
b’foo {0} ‘. Format (obj), это будет работать в Python 2, но потерпит неудачу в Python 3. Лучшее, что я придумал для этого, это вместо этого использовать конкатенацию, или выполните форматирование с использованием юникодов, а затем закодируйте их в свой байтовый объект (но тогда вы получите дополнительное удовольствие от выбора подходящей кодировки!).

Знаете ли вы, что
модуль
re может сканировать юникоды или байты в Python 3? Переключение производится путем передачи либо
байтовшаблон или шаблон
str , а затем передача в соответствующий тип объекта для анализа. Но если вы используете
префикс
r » (т. Е. Необработанные строки) для более разумной обработки обратной косой черты, у вас возникает другая проблема, когда вы хотите проанализировать байты. Python не поддерживает
rb » -prefixes, то есть вы можете иметь как строковые литералы, так и байтовые строковые литералы, но не оба. Вы должны отказаться от того или другого, и я обычно опускаюсь на сторону, чтобы бросить необработанные нити и страдать от разрастания обратной косой черты.

Часть кода, который я переносил, использовала
itertools.izip_longest () , но его нет в Python 3. Вместо этого у вас есть
itertools.zip_longest (), Вам придется выполнить условный импорт (то есть попробовать-кроме), чтобы получить правильную версию.

Вы используете
zope.interfaces ? Вам будет интересно узнать, что синтаксис, к которому мы давно привыкли заявлять, что класс реализует интерфейс, не работает в Python 3. Например:

    from zope.interface import Interface, implements
    class MyInterface(Interface):
        pass
    class MyClass:
        implements(MyInterface)

Это связано с тем, что в Python 3 не работает взлом стека, который
реализует () . К счастью, в последней версии zope.interface появился новый декоратор классов, который вы можете использовать вместо этого. Это работает и в Python 2.6 и 2.7, поэтому измените код, чтобы использовать это:

    from zope.interface import Interface, implementer
    class MyInterface(Interface):
        pass
    @implementer(MyInterface)
    class MyClass:
        pass

Мне все равно нравится использование декораторов классов.

Вот хитрый. Знаете ли вы, что Python 2 предоставляет некоторые кодеки для выполнения интересных преобразований, таких как вращение Caeser (то есть, rot13)? Таким образом, вы можете делать такие вещи, как:

    >>> 'foo'.encode('rot-13')
    'sbb'

Это не работает в Python 3, потому что, хотя определенные кодеки str-to-str, такие как rot-13, все еще существуют,
интерфейс
str.encode () требует, чтобы кодек возвращал объект байтов. Чтобы использовать кодеки str-to-str как в Python 2, так и в Python 3, вам придется открыть капот и использовать API более низкого уровня, получая и вызывая кодек напрямую:

    >>> from codecs import getencoder
    >>> encoder = getencoder('rot-13')
    >>> rot13string = encoder(mystring)[0]

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

Это все на данный момент. Удачного портирования!


Источник: http://www.wefearchange.org/2012/01/python-3-porting-fun-redux.html