Рекомендации к переходу на Python 3

Содержание

  1. Цель руководства
  2. О используемых библиотеках
  3. Основные отличия Python 3.x от 2.х
  4. Полезные ссылки

Цель руководства

Руководство является рекомендацией по переносу кода на версию языка 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.

Основные отличия Python 3.x от 2.х

print

Выражение 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

dict

Методы 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 в объектах словаря.

Что делать:

  • В случаях, где используются методы списка:

    • заменить их на функции как в случае с методом списка sort и функцией sorted
    • создать список из итератора явным вызовом функции list
  • В местах использования результатов этих функций как итерируемых объектов (как при проходе по ним с помощью цикла for item in d.items()) оставить всё как есть

  • Заменить вызов метода has_key на выражение(инструкцию) in.

  • При создании классов, объекты которого будут иметь интерфейс объектов dict, вместо метода has_key описывать метод __contains__

map, filter

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 для коротких последовательностей?

range, xrange (zip, izip)

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()

Что делать:

  • Не сравнивать! Если программа работает только из-за того что числа всегда больше чем None, то это странная программа!

sorted(), list.sort()

Не поддерживают аргумент cmp.

Что делать:

  • Переписать код с использованием аргумента key
  • Или использовать функцию functools.cmp_to_key
    sorted(iterable, key=cmp_to_key(locale.strcoll))
    

cmp

Функции cmp() в python 3.x нет. Специальный метод __cmp__() перестал использоваться.

Что делать:

  • Вместо функции cmp() можно использовать выражение (a > b) - (a < b)
  • Вместо метода __cmp__() нужно использовать метод __lt__() для сортировки

int, long

Тип long теперь называется int.

Что делать:

  • Не использовать литерал L в конце чисел
  • В некоторых местах пригодится integer_types из библиотеки six

    python 2.x:

    >>> isinstance(10, (int, long))
    True
    

    python 3.x:

    >>> isinstance(10, six.integer_types)
    True
    

Оператор “/”

Оператор / теперь всегда возвращает float независимо от типов операндов

Что делать:

  • Если нужно целочисленное деление то использовать оператор //
  • Для использования в Python 2.x нового оператора деления добавить в импорт division
    from __future__ import division
    

unicode, str, bytes

Теперь есть только один строковый тип - 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

Что делать:

  • Переделать все относительные импорты на from .

Метаклассы

Изменился синтаксис задания метакласса классу

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

Функция callable удалена в python 3.x.

python 2.x:

>>> callable(map)
        True

python 3.x:

>>> isinstance(map, collections.Callable)
    True

Что делать:

  • Использовать функцию callable из библиотеки six
    six.callable(map)
    

Исключения

В python 3.x синтаксис обработки исключений типа except , err: - запрещён. Вместо запятой обязательно использовать ключевое слово as.

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

Что делать:

  • Для совместимости с Python 2.x эти модули можно импортировать из библиотеки six
    from six.moves import cStringIO
    

Полезные ссылки

http://docs.python.org/3.0/whatsnew/3.0.html

http://docs.python.org/dev/howto/pyporting

http://python3porting.com/

https://docs.djangoproject.com/en/dev/topics/python3/

http://pythonhosted.org/six/

Contents © 2014 БАРС Груп - Powered by Nikola