Содержание страницы
Перенос кода с Python 2 на Python 3¶Porting 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-porting с вопросами.
Краткое объяснение¶The Short Explanation
Чтобы сделать ваш проект совместимым с Python 2 и 3 из единой исходной базы, основные шаги следующие:
- Сосредоточьтесь на поддержке только Python 2.7
- Убедитесь, что у вас хорошее покрытие тестами (в этом поможет coverage.py; pip install coverage)
- Изучите различия между Python 2 и 3
- Воспользуйтесь Modernize или Futurize, чтобы обновить свой код (соответственно pip install modernize или pip install future)
- Используйте Pylint, чтобы убедиться, что вы не откатываетесь назад в поддержке Python 3 (pip install pylint)
- Воспользуйтесь caniusepython3, чтобы узнать, какие из ваших зависимостей блокируют использование Python 3 (pip install caniusepython3)
- Когда зависимости перестанут быть помехой, используйте непрерывную интеграцию, чтобы сохранять совместимость с Python 2 и 3 (tox поможет тестировать с несколькими версиями Python; pip install tox)
Если вы полностью отказываетесь от поддержки Python 2, то после изучения различий между Python 2 и 3 можете запустить 2to3 для своего кода и пропустить остальные шаги, описанные выше.
Подробности¶Details
A key point about supporting Python 2 & 3 simultaneously is that you can start today! Even if your dependencies are not supporting Python 3 yet that does not mean you can’t modernize your code now to support Python 3. Most changes required to support Python 3 lead to cleaner code using newer practices even in Python 2.
Ещё один ключевой момент: модернизация вашего кода Python 2 для поддержки Python 3 в значительной степени автоматизирована. Хотя, возможно, вам придётся принять некоторые решения по API из-за того, что Python 3 разграничивает текстовые и двоичные данные, но низкоуровневая работа теперь в основном сделана за вас, и поэтому вы можете сразу же воспользоваться преимуществами автоматизированных изменений.
Помните эти ключевые моменты, пока читаете дальше о деталях переноса вашего кода для поддержки Python 2 и 3 одновременно.
Прекратите поддержку Python 2.6 и старше¶Drop support for Python 2.6 and older
Хотя можно заставить Python 2.5 работать с Python 3, гораздо проще, если работать только с Python 2.7. Если отказ от Python 2.5 невозможен, проект six поможет одновременно поддерживать Python 2.5 и 3 (pip install six). Однако имейте в виду, что почти все проекты, перечисленные в этом руководстве, будут вам недоступны.
Если вы можете пропустить Python 2.5 и старше, то необходимые изменения в вашем коде должны по-прежнему выглядеть и ощущаться как идиоматичный код Python. В худшем случае вам придётся использовать функцию вместо метода в некоторых случаях или импортировать функцию вместо использования встроенной, но в остальном общая трансформация не должна казаться вам чуждой.
Но старайтесь поддерживать только Python 2.7. Python 2.6 больше не поддерживается и не получает исправлений ошибок. Это означает, что вам придётся обходить любые проблемы, возникающие при работе с Python 2.6. Некоторые инструменты, упомянутые в этом руководстве, также не поддерживают Python 2.6 (например, Pylint), и со временем таких будет всё больше. Вам будет просто проще, если вы поддерживаете только те версии Python, которые действительно необходимо.
Make sure you specify the proper version support in your setup.py file¶
In your setup.py file you should have the proper trove classifier specifying what versions of Python you support. As your project does not support Python 3 yet you should at least have Programming Language :: Python :: 2 :: Only specified. Ideally you should also specify each major/minor version of Python that you do support, e.g. Programming Language :: Python :: 2.7.
Обеспечьте хорошее тестовое покрытие¶Have good test coverage
Когда ваш код поддерживает самую старую версию Python 2, которую вы хотите, убедитесь, что тестовый набор имеет хорошее покрытие. Хорошее практическое правило: нужно, чтобы тесты были настолько надёжными, чтобы любые сбои, возникающие после автоматической переработки кода, были реальными ошибками инструментов, а не вашего кода. Если хотите ориентироваться на число, стремитесь к покрытию более 80% (и не расстраивайтесь, если не удаётся легко достичь 90%). Если у вас ещё нет инструмента для измерения покрытия тестами, рекомендуется coverage.py.
Изучите различия между Python 2 и 3¶Learn the differences between Python 2 & 3
Once you have your code well-tested you are ready to begin porting your code to Python 3! But to fully understand how your code is going to change and what you want to look out for while you code, you will want to learn what changes Python 3 makes in terms of Python 2. Typically the two best ways of doing that is reading the “What’s New” doc for each release of Python 3 and the Porting to Python 3 book (which is free online). There is also a handy cheat sheet from the Python-Future project.
Обновите свой код¶Update your code
Once you feel like you know what is different in Python 3 compared to Python 2, it’s time to update your code! You have a choice between two tools in porting your code automatically: Modernize and Futurize. Which tool you choose will depend on how much like Python 3 you want your code to be. Futurize does its best to make Python 3 idioms and practices exist in Python 2, e.g. backporting the bytes type from Python 3 so that you have semantic parity between the major versions of Python. Modernize, on the other hand, is more conservative and targets a Python 2/3 subset of Python, relying on six to help provide compatibility.
Независимо от выбранного инструмента, он обновит код для работы под Python 3, сохраняя совместимость с исходной версией Python 2. В зависимости от желаемой степени консервативности можно сначала прогнать инструмент по набору тестов и визуально проверить diff, чтобы убедиться в корректности преобразования. После преобразования тестов и подтверждения, что все тесты по-прежнему проходят, можно преобразовывать код приложения, зная, что любой упавший тест – это ошибка перевода.
Unfortunately the tools can’t automate everything to make your code work under Python 3 and so there are a handful of things you will need to update manually to get full Python 3 support (which of these steps are necessary vary between the tools). Read the documentation for the tool you choose to use to see what it fixes by default and what it can do optionally to know what will (not) be fixed for you and what you may have to fix on your own (e.g. using io.open() over the built-in open() function is off by default in Modernize). Luckily, though, there are only a couple of things to watch out for which can be considered large issues that may be hard to debug if not watched for.
Деление¶Division
In Python 3, 5 / 2 == 2.5 and not 2; all division between int values result in a float. This change has actually been planned since Python 2.2 which was released in 2002. Since then users have been encouraged to add from __future__ import division to any and all files which use the / and // operators or to be running the interpreter with the -Q flag. If you have not been doing this then you will need to go through your code and do two things:
- Add from __future__ import division to your files
- Обновить все операторы деления по необходимости: либо использовать // для целочисленного деления, либо продолжать использовать / и ожидать число с плавающей запятой
The reason that / isn’t simply translated to // automatically is that if an object defines a __truediv__ method but not __floordiv__ then your code would begin to fail (e.g. a user-defined class that uses / to signify some operation but not // for the same thing or at all).
Текстовые и бинарные данные¶Text versus binary data
In Python 2 you could use the str type for both text and binary data. Unfortunately this confluence of two different concepts could lead to brittle code which sometimes worked for either kind of data, sometimes not. It also could lead to confusing APIs if people didn’t explicitly state that something that accepted str accepted either text or binary data instead of one specific type. This complicated the situation especially for anyone supporting multiple languages as APIs wouldn’t bother explicitly supporting unicode when they claimed text data support.
Чтобы сделать различие между текстом и двоичными данными более чётким и заметным, Python 3 сделал то, что сделало большинство языков, созданных в эпоху интернета: текст и двоичные данные стали разными типами, которые нельзя слепо смешивать (Python появился до широкого доступа к интернету). Для кода, который работает только с текстом или только с двоичными данными, такое разделение не является проблемой. Но для кода, который должен работать и с тем, и с другим, это означает, что теперь, возможно, придётся следить за тем, когда вы используете текст, а когда – двоичные данные, поэтому этот процесс нельзя полностью автоматизировать.
To start, you will need to decide which APIs take text and which take binary (it is highly recommended you don’t design APIs that can take both due to the difficulty of keeping the code working; as stated earlier it is difficult to do well). In Python 2 this means making sure the APIs that take text can work with unicode in Python 2 and those that work with binary data work with the bytes type from Python 3 and thus a subset of str in Python 2 (which the bytes type in Python 2 is an alias for). Usually the biggest issue is realizing which methods exist for which types in Python 2 & 3 simultaneously (for text that’s unicode in Python 2 and str in Python 3, for binary that’s str/bytes in Python 2 and bytes in Python 3). The following table lists the unique methods of each data type across Python 2 & 3 (e.g., the decode() method is usable on the equivalent binary data type in either Python 2 or 3, but it can’t be used by the text data type consistently between Python 2 and 3 because str in Python 3 doesn’t have the method).
| Текстовые данные | Бинарные данные |
| __mod__ (оператор %) | |
| decode | |
| encode | |
| формат | |
| isdecimal | |
| isnumeric |
Упростить различение можно с помощью кодирования и декодирования между бинарными данными и текстом на границах кода. Это означает, что при получении текста в бинарном виде его следует сразу декодировать. А если коду нужно отправить текст в бинарном виде, то кодировать его как можно позже. Это позволяет коду работать внутри только с текстом и избавляет от необходимости отслеживать, с каким типом данных ведётся работа.
The next issue is making sure you know whether the string literals in your code represent text or binary data. At minimum you should add a b prefix to any literal that presents binary data. For text you should either use the from __future__ import unicode_literals statement or add a u prefix to the text literal.
As part of this dichotomy you also need to be careful about opening files. Unless you have been working on Windows, there is a chance you have not always bothered to add the b mode when opening a binary file (e.g., rb for binary reading). Under Python 3, binary files and text files are clearly distinct and mutually incompatible; see the io module for details. Therefore, you must make a decision of whether a file will be used for binary access (allowing to read and/or write binary data) or text access (allowing to read and/or write text data). You should also use io.open() for opening files instead of the built-in open() function as the io module is consistent from Python 2 to 3 while the built-in open() function is not (in Python 3 it’s actually io.open()).
The constructors of both str and bytes have different semantics for the same arguments between Python 2 & 3. Passing an integer to bytes in Python 2 will give you the string representation of the integer: bytes(3) == '3'. But in Python 3, an integer argument to bytes will give you a bytes object as long as the integer specified, filled with null bytes: bytes(3) == b'\x00\x00\x00'. A similar worry is necessary when passing a bytes object to str. In Python 2 you just get the bytes object back: str(b'3') == b'3'. But in Python 3 you get the string representation of the bytes object: str(b'3') == "b'3'".
Finally, the indexing of binary data requires careful handling (slicing does not require any special handling). In Python 2, b'123'[1] == b'2' while in Python 3 b'123'[1] == 50. Because binary data is simply a collection of binary numbers, Python 3 returns the integer value for the byte you index on. But in Python 2 because bytes == str, indexing returns a one-item slice of bytes. The six project has a function named six.indexbytes() which will return an integer like in Python 3: six.indexbytes(b'123', 1).
Подводя итог:
- Определить, какие API принимают текст, а какие – бинарные данные
- Убедитесь, что код, работающий с текстом, также работает с unicode, а код для бинарных данных работает с bytes в Python 2 (см. таблицу выше, чтобы узнать, какие методы нельзя использовать для каждого типа)
- Помечайте все бинарные литералы префиксом b, используйте префикс u или импорт __future__ для текстовых литералов
- Декодировать бинарные данные в текст как можно раньше, кодировать текст в бинарные данные как можно позже
- Открывайте файлы с помощью io.open() и не забывайте указывать режим b, когда это необходимо
- Будьте осторожны при индексации бинарных данных
Предотвращайте регрессии совместимости¶Prevent compatibility regressions
После того как вы полностью перевели свой код для совместимости с Python 3, вы захотите убедиться, что код не регрессирует и не перестаёт работать под Python 3. Это особенно важно, если у вас есть зависимость, которая в данный момент мешает вам запускать код на Python 3.
Чтобы помочь с сохранением совместимости, любые новые модули, которые вы создаёте, должны содержать как минимум следующий блок кода в начале:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
Вы также можете запускать Python 2 с флагом -3, чтобы получать предупреждения о различных проблемах совместимости, которые ваш код вызывает во время выполнения. Если превратить предупреждения в ошибки с помощью -Werror, можно быть уверенным, что ни одно предупреждение не останется незамеченным.
Также можно использовать проект Pylint и его флаг --py3k для статического анализа кода и получения предупреждений, когда код начинает отходить от совместимости с Python 3. Это избавляет от необходимости регулярно запускать Modernize или Futurize для выявления регрессий совместимости. Однако это требует поддержки только Python 2.7 и Python 3.4 или новее, так как это минимальные версии Python, поддерживаемые Pylint.
Проверьте, какие зависимости блокируют ваш переход¶Check which dependencies block your transition
После того как вы сделали свой код совместимым с Python 3, стоит начать проверять, портированы ли ваши зависимости. Проект caniusepython3 был создан, чтобы помочь определить, какие проекты – прямо или косвенно – мешают вам поддерживать Python 3. Существует как инструмент командной строки, так и веб-интерфейс по адресу https://caniusepython3.com.
Проект также предоставляет код, который вы можете интегрировать в свой набор тестов, чтобы у вас был падающий тест, когда у вас больше нет зависимостей, блокирующих использование Python 3. Это позволяет избежать ручной проверки зависимостей и быстро получать уведомление, когда можно начинать запускать код на Python 3.
Обновите файл setup.py, чтобы указать совместимость с Python 3¶Update your setup.py file to denote Python 3 compatibility
Как только ваш код заработает на Python 3, обновите классификаторы в setup.py, чтобы они содержали Programming Language :: Python :: 3 и не указывали только поддержку Python 2. Это сообщит всем пользователям вашего кода, что вы поддерживаете Python 2 и 3. В идеале также стоит добавить классификаторы для каждой основной/минорной версии Python, которую вы теперь поддерживаете.
Используйте непрерывную интеграцию для сохранения совместимости¶Use continuous integration to stay compatible
Когда вы сможете полностью запускать код на Python 3, вы захотите убедиться, что ваш код всегда работает как на Python 2, так и на Python 3. Вероятно, лучший инструмент для запуска тестов в нескольких интерпретаторах Python – это tox. Затем вы можете интегрировать tox с вашей системой непрерывной интеграции, чтобы случайно не нарушить поддержку Python 2 или 3.
Также можно использовать флаг -bb в интерпретаторе Python 3, чтобы вызывать исключение при сравнении байтов со строками. Обычно результатом будет просто False, но если вы ошиблись в разделении обработки текстовых и бинарных данных, можете случайно сравнивать текст и бинарные данные. Этот флаг вызовет исключение в таких случаях, помогая отследить подобные ошибки.
И это в основном всё! На данный момент ваша кодовая база совместима одновременно и с Python 2, и с Python 3. Ваше тестирование также будет настроено так, что вы случайно не нарушите совместимость с Python 2 или 3, независимо от того, какую версию вы обычно используете для запуска тестов во время разработки.
Полный отказ от поддержки Python 2¶Dropping Python 2 support completely
Если есть возможность полностью отказаться от поддержки Python 2, то шаги, необходимые для перехода на Python 3, значительно упрощаются.
- Обновите код так, чтобы он поддерживал только Python 2.7
- Убедитесь, что у вас хорошее покрытие тестами (coverage.py может помочь)
- Изучите различия между Python 2 и 3
- Используйте 2to3 для переписывания кода, чтобы он работал только под Python 3
После этого ваш код будет полностью совместим с Python 3, но не будет поддерживаться Python 2. Также обновите классификаторы в setup.py, чтобы они содержали Programming Language :: Python :: 3 :: Only.