Руководство является рекомендацией по переносу кода на версию языка Python 3.x . В руководстве приведены основные способы и приемы, используемые при портировании проектов.
Цели руководства: предоставить советы и рекомендации по изменению кода для его одинакового выполнения разными версиями интерпретатора Python (2.6, 2.7, 3.2, 3.3);
Для наиболее легкого написания кода одинаково работающего как в Python 2.6, 2.7 так и в Python 3.2, 3.3 рекомендуется использовать бибилотеку six.
В ней собрано большее количество инструментов позволяющих писать кросверсионный код для Python 2.x-3.x. Для нахождения мест подлежащих изменению рекомендуется воспользоваться утилитами 2to3 или python-modernize.
Выражение print заменено функцией print():
python 2.x:
print 'something' print 'something', # Запятая в конце - без перехода на новую строку print >>sys.stderr, 'fatal error'
python 3.x:
print('something') print('something', end='') # end - keyword-аргумент print('fatal error', file=sys.stderr)
Что делать:
Каждый модуль, где используется print добавить импорт print_function, что позволит в Python 2.x использовать новую функцию print
from __future__ import print_function
Заменить выражение print на вызов функции print_function
Методы items(), values(), keys() у объектов dict теперь возвращают не список, а итерируемый объект. Поэтому необходимо следить за результатом вызовов этих методов, например:
python 2.x:
list_ = dict_.items() list_.sort()
python 3.x:
list_ = sorted(dict_.items())
Также методы iterkeys(), iteritems() and itervalues() теперь не поддерживаются в python 3.x. Так же нет метода has_key в объектах словаря.
Что делать:
В случаях, где используются методы списка:
В местах использования результатов этих функций как итерируемых объектов (как при проходе по ним с помощью цикла for item in d.items()) оставить всё как есть
Заменить вызов метода has_key на выражение(инструкцию) in.
При создании классов, объекты которого будут иметь интерфейс объектов dict, вместо метода has_key описывать метод __contains__
map() и filter() возвращают итераторы в python 3.x. Теперь нельзя использовать map() для side-эффектов функций не возвращающих результат. Если передаваемые последовательности не одинаковой длины map() остановит обработку на кратчайшей из последовательностей.
python 2.x:
>>> map(lambda x: x, [1,2,3,4]) [1,2,3,4] >>> a = [] >>> map(a.append, [1,2,3,4]) [None, None, None, None] >>> a [1,2,3,4] >>> for item in map(lambda x, y: (x, y), [1,2,3,4], [1,2]): ... print(item) (1, 1) (2, 2) (3, None) (4, None)
python 3.x:
>>> map(lambda x: x, [1,2,3,4]) <map object at 0xb6e99a8c> >>> a = [] >>> map(a.append, [1,2,3,4]) <map object at 0xb6e99acc> >>> a [] >>> for item in map(lambda x, y: (x, y), [1,2,3,4], [1,2]): ... print(item) (1, 1) (2, 2)
Что делать:
Если в этом месте действительно нужен список, то самый быстрый способ исправить это - обернуть в list():
list(map(...))
Однако лучше использовать списковые выражения или переписать код так чтобы не нуждаться в списке вообще.:
# Было map(lambda x:x + 1, [1,2,3,4]) # Стало [x + 1 for x in [1,2,3,4]]
Не использовать map для side-эффектов функций. Вместо этого корректнее использовать цикл for:
for item in [1,2,3,4]: a.append(item)
Честно? Кто-то пользовался тем, что map() добавляет None для коротких последовательностей?
xrange и itertools.izip (Lazy-реализация range и zip, соответственно, возвращающая итератор вместо списка) теперь называется range и zip, старый range/zip - отменён за ненадобностью.
python 2.x:
>>> range(4) # список [0, 1, 2, 3] >>> xrange(4) # итератор xrange(4) >>> zip([1,2],[3,4]) # список [(1, 3), (2, 4)] >>> itertools.izip([1,2],[3,4]) # итератор <itertools.izip object at 0x911194c>
python 3.x:
>>> range(4) # итератор range(0, 4) >>> xrange(4) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'xrange' is not defined >>> zip([1,2],[3,4]) # итератор <zip object at 0xb6ec9aec> >>> itertools.izip([1,2],[3,4]) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'module' object has no attribute 'izip'
Что делать:
Если нет особой необходимости в использовании lazy реализации xrange/itertools.izip, то достаточно заменить везде xrange на range.
Если такая необходимость есть (например при больших числах), тогда необходимо импортировать xrange/zip из бибилиотеки six:
from six.moves import xrange from six.moves import zip
Если результатом должен быть обязательно список, то можно обернуть в list():
list(range(4)) list(zip(...))
Операторы сравнения теперь возбуждают исключение TypeError при сравнении разных типов данных
python 2.x:
>>> 1 > None True
python 3.x:
>>> 1 > None Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unorderable types: int() > NoneType()
Что делать:
Не поддерживают аргумент cmp.
Что делать:
sorted(iterable, key=cmp_to_key(locale.strcoll))
Функции cmp() в python 3.x нет. Специальный метод __cmp__() перестал использоваться.
Что делать:
Тип long теперь называется int.
Что делать:
В некоторых местах пригодится integer_types из библиотеки six
python 2.x:
>>> isinstance(10, (int, long)) True
python 3.x:
>>> isinstance(10, six.integer_types) True
Оператор / теперь всегда возвращает float независимо от типов операндов
Что делать:
from __future__ import division
Теперь есть только один строковый тип - str, схожий по поведению с типом unicode из Python 2.x. Базового типа для строк basestring в python 3 не существует. Все строки превращаются в юникодные. Литерал u для юникодных строк теперь не обязателен (Его добавили только в версии python 3.3, как часть обратной совместимости).
Что делать:
Для сохранения совместимости с Python 2.x заменить str и unicode на text_type из библиотеки six. text_type будет равен unicode для Python 2.x и str для Python 3.x
Так же заменить basestring на string_types из библиотеки six. string_types равен (basestring,) для Python 2.x и (str, ) для Python 3.x
Теперь импорты модулей, находящихся в том же пакете, что и модуль, откуда производиться импортирование, должны начинаться с точки.
python 2.x:
>>> from models import BaseModel >>> import models
python 3.x:
>>> from .models import BaseModel >>> from . import models
Что делать:
Изменился синтаксис задания метакласса классу
python 2.x:
class C(A): __metaclass__ = M
python 3.x:
class C(A, metaclass=M): pass
Что делать:
Заменить атрибут класса __metaclass__ на наследование от результата функции with_metaclass из библиотеки six
Конструкцию вида:
class C(A, B): __metaclass__ = M
Заменить на:
class C(six.with_metaclass(M, A), B):
Функция callable удалена в python 3.x.
python 2.x:
>>> callable(map) True
python 3.x:
>>> isinstance(map, collections.Callable) True
Что делать:
six.callable(map)
В python 3.x синтаксис обработки исключений типа except
python 2.x:
try: a=b/c except ZeroDivisionError, err: print err
python 3.x:
try: a=b/c except ZeroDivisionError as err: print err
Что делать:
В Python 3.x были перемещены/переименованны некоторые модули.
python 2.x:
from cStringIO import StringIO
python 3.x:
from io import StringIO
Что делать:
from six.moves import cStringIO
http://docs.python.org/3.0/whatsnew/3.0.html
http://docs.python.org/dev/howto/pyporting