- PVSM.RU - https://www.pvsm.ru -
Хочу поделиться опытом портирования проекта с Python 2.7 на Python 3.5. Необычными засадами и прочими интересными нюансами.
Немного о проекте:
Портирование проводилось с помощью утилиты 2to3 [1] с последующим восстановлением работоспособности тестов. Сколько это заняло времени сказать сложно, проект — хобби — занимаюсь им в свободное время.
После обработки исходников очень рекомендую вычитать изменения, поскольку производительность — это не то, что ставится во главу угла при конвертировании.
Также есть вероятность, что некоторые ваши имена пересекутся с удаляемыми/изменяемыми методами. Например, 2to3 изменила код, который работал с моим методом has_key моего же класса (этот метод есть у словаря Python 2 и удалён в Python 3).
Итак, о что можно споткнуться, если начать двигать прогресс в сторону Python 3. Начну с самого интересного.
«ЧЕЕЕЕГОООО?!?» о_О
Примерно такой была моя реакция, когда, разбираясь с очередным тестом, я увидел в консоли следующее:
round(1.5)
2
round(2.5)
2
«Банковское» округление — округление к ближайшему чётному. Это новые правила округления, заменившие «школьное» округление в большую сторону.
Смысл «банковского» округления в том, что при работе с большим количеством данных и сложных вычислениях оно сокращает вероятность накопления ошибки. В отличие от обычного «школьного» округления, которое всегда приводит половинчатые значения к большему числу.
Для большинства это изменение не критично, но оно может привести к совсем неожиданному изменению поведения программы. В моём случае, например, изменилось расположение дорог на игровой карте.
round(1.65, 1)
1.6
round(1.55, 1)
1.6
Если вы полагались на целочисленную арифметику с типом int (когда 1/4 == 0
), то готовьтесь к длительному вычитыванию кода, поскольку теперь 1/4 == 0.25
и провести автоматическую замену /
на //
(оператор целочисленного деления) не получится из-за отсутствия информации о типах переменных.
Guido van Rossum подробно объяснил причину этого изменения [2].
Изменилось поведение функции map при итерации по нескольким последовательностям.
None
.Python 2:
map(lambda x, y: (x, y), [1, 2], [1])
[(1, 1), (2, None)]
Python 3:
list(map(lambda x, y: (x, y), [1, 2], [1]))
[(1, 1)]
Приведённый ниже код будет работать в Python 2, но вызовет исключение NameError: name 'x' is not defined
в Python 3:
class A(object):
x = 5
y = [x for i in range(1)]
Это связано с изменениями в областях видимости генераторов, списковых выражений и классов. Подробный разбор на Stackoverflow [3].
Но будет работать следующий код:
def make_y(x): return [x for i in range(1)]
class A(object):
x = 5
y = make_y(x)
Если вы полагались на наличие или отсутствие методов с конкретными именами, то могут возникнуть неожиданные проблемы. Например, в одном месте, где творилась чёрная волшба, я отличал строки от списков по наличию метода __iter__
. В Python 2 его у строк нет, в Python 3 он появился и код сломался.
Некоторые операции, которые по умолчанию работали в Python 2, перестали работать в Python 3. В частности, запрещено сравнение объектов без явно заданных методов сравнения.
Выражение object() < object()
:
True
или False
(в зависимости от «identity» объектов).TypeError: unorderable types: object() < object()
.Думаю их много разных, но я столкнулся с изменением поведения словаря. Следующий код будет иметь разные эффекты в Python 2 и Python 3:
D = {'a': 1,
'b': 2,
'c': 3}
print(list(D.values()))
В Python 2 он всегда печатает [1, 3, 2]
(или, как минимум, одинаковую последовательность для конкретной сборки Python на конкретной машине).
В Python 3 последовательность элементов отличается при каждом запуске. Соответственно, результаты выполнения кода, полагавшегося на эту «фичу» станут отличаться.
Конечно, я не полагался специально на фиксированную последовательность элементов в словаре, но, как оказалось, сделал это неявно.
К сожалению, из-за совмещения портирования, переезда на новый сервер и рефакторинга сделать конкретные замеры не получилось.
Мой главный вывод — Python стал более идиоматичным:
В коде стало легче обнаружить семантические ошибки, которые в былые времена могли прятаться годами.
Второй вывод: если вы завязаны на математические операции, лучше начинать реализовывать их сразу в правильном для Python 3 ключе, даже если вы собираетесь тянуть с переездом до 20-ого года.
Пишите код на Python 2 с использованием __future__ [4] и никаких проблем с переездом не будет.
Автор: Tiendil
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/225626
Ссылки в тексте:
[1] 2to3: https://docs.python.org/2/library/2to3.html
[2] объяснил причину этого изменения: http://python-history.blogspot.com.by/2009/03/problem-with-integer-division.html
[3] Подробный разбор на Stackoverflow: http://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition
[4] __future__: https://docs.python.org/2/library/__future__.html
[5] Источник: https://habrahabr.ru/post/318384/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.