Python 2 vs Python 3

https://sebastianraschka.com/Articles/2014_python_2_3_key_diff.html

https://sealedabstract.com/rants/python-3-is-fine/

https://docs.python.org/2.7/howto/pyporting.html

https://docs.python.org/3/howto/pyporting.html

WPROWADZENIE

Kiedy chcemy mieć kompatybilność Py2 i Py3, to trzeba się zdecydować na Py2.7, bo jest dalej wspierany (od 2020 już nie). Oto szereg uwag i wskazówek jak tworzyć kod działający niezależnie od wersji Pythona.


Moduł __future__ pozwala wprowadzać do Py2 elementy z Py3.
UWAGI
Wydaje mi się, że nie jest dobrze łączyć w ten sposób dwóch światów
Py2 i Py3. Można przeoczyć import i być zaskoczonym nowym działaniem.

Py2: instrukcja print (ang. print statement). 
Py3: funkcja print().
UWAGI
print(item1, item2) w Py2 wypisze krotkę.
Kompatybilna forma to
print("one {} two {}".format(item1, item2))

Czasem zalecane jest użycie importu (Py2.6+):
from __future__ import print_function

Integer division. 
UWAGI
Dla liczb całkowitych lepiej używać a // b.
Jeżeli chcemy mieć wynik float, to lepiej zawsze pisać 3/2.0
lub float(x)/2 (a nie 3/2 lub x/2).
Taki zapis nie zmyli usera z Py2.

If you need to support both Py2 and Py3 use:
from __future__ import division

We własnych klasach trzeba pamiętać o metodach:
__div__  dzielenie klasyczne (x/y)
__floordiv__  dzielenie całkowite, x // y
__truediv__ działa w Py2 jeżeli jest aktywne __future__.division

Py2: jest int i long.
Py3: jest tylko int.
Sprawdzanie typów liczbowych można zrobić przez try/except:
try:
    integer_types = (int, long)
except NameError:   # Py3
    integer_types = (int,)
# Zastosowanie: isinstance(variable, integer_types)

Py2: jest str(), unicode(), bytearray(), nie ma byte().
Py3: str() dla Unicode, byte() i bytearray() dla bajtów.
UWAGI
W bibliotece stosujemy czyste ASCII i str() lub repr() do napisów.

W Py2 jest iterator xrange(), a range() tworzy listę.
W Py3 nie ma xrange(), a range() jest iteratorem.
UWAGI
Listę można tworzyć uniwersalnie przez list(range(n)).
Z iteratorem jest problem.

Jest moduł six, który w Py3 podobno posiada xrange(), 
ale nie ma go w bibliotece standardowej.
Moje podejście:
try:
    integer_types = (int, long)
    range = xrange   # range będzie zawsze generatorem
except NameError:   # Py3
    integer_types = (int,)

W Py3 nie ma wbudowanej funkcji cmp().
Obejście proponowane w oficjalnej dokumentacji Pythona ma postać
cmp = lambda x, y: (x > y) - (x < y)
Moje podejście:
try:
    integer_types = (int, long)
except NameError:   # Py3
    integer_types = (int,)
    cmp = lambda x, y: (x > y) - (x < y)

Sorting, list.sort() i sorted().
Py2: są dwa parametry cmp i key.
Py3: jest tylko key.
UWAGI
Trzeba używać parametru key.
Nie można bezpośrednio użyć funkcji porównującej cmp(x, y).
Rozwiązaniem w Py3 jest funkcja cmp_to_key() z modułu functools.

from functools import cmp_to_key   # Py3.2+

alist.sort(key=cmp_to_key(cmp_function))

The __contains__ method for range objects in Py3.
UWAGI
Przyjmuję, że wyszukiwanie w liście jest wolne O(n) i zwykle korzystam
ze zbioru lub słownika.

Raising exceptions. 
Py2 dopuszcza starą składnię z przecinkiem:
raise IOError, "file error"
UWAGI
Zawsze stosować nową składnię z nawiasem (jak konstruktor):
raise IOError("file error")

Handling exceptions.
Python poniżej 2.6 dopuszcza starą składnię z przecinkiem:
try:
    let_us_cause_a_NameError
except NameError, exception:
    print exception, '--> our error message'
UWAGI
Zawsze stosuję nową składnię ze słowem kluczowym as (od Py2.6)
try:
    let_us_cause_a_NameError
except NameError as exception:
    print("{} --> our error message".format(exception))

The next() function (Py3) and .next() method (Py2).
UWAGI
W Py2.6 pojawiła się wbudowana funkcja next(), więc kod może
być uniwersalny:
my_generator = (letter for letter in 'abcdefg')
next(my_generator)

For-loop variables and the global namespace leak.
i = 1
[i for i in range(5)]
print(i)   # 4 w Py2, 1 w Py3
UWAGI
Bezpieczniej używać lepszych nazw zmiennych, np.
item = 1
[i for i in range(5)]

Comparing unorderable types.
Py2 pozwala porównywać różne typy, np.
[1,2] > "a"  zwraca False.
W Py3 wyzwalany jest TypeError, co jest dobrym ostrzeżeniem.
UWAGI
Takiego porównania nie powinno się stosować w Py2, więc nie jest to problem.

Parsing user inputs via input().
W Py3 input() odpowiada raw_input() z Py2 i zwraca str().
W Py2 input() było niebezpieczne bo próbowało wykonywać kod.
UWAGI
To trzeba osobno opracować w Py2 i Py3.
Albo można skorzystać z try/except i zawsze używac input():
try:
    input = raw_input
except NameError:   # jesteśmy w Py3
    pass

Returning iterable objects instead of lists.
Zmiany w funkcjach: 
zip(), map(), filter(), D.keys(), D.values(), D.items(), ...
W Py3 słowniki nie mają metody D.has_key(k), lepiej zawsze używać
operatora in (k in D).
list(map(...)) zawsze zrobi listę.
UWAGI
Możliwe obejście problemu jest przez try/except:
try:
    values = D.itervalues()
except AttributeError:   # jesteśmy w Py3
    values = D.values()

Banker’s Rounding
round(15.5)   # 16.0 w Py2, 16 w Py3
round(16.5)   # 17.0 w Py2, 16 w Py3
UWAGI
https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
https://en.wikipedia.org/wiki/IEEE_floating_point#Roundings_to_nearest

Moduł Queue z Py2 nazywa się queue w Py3.

# Use feature detection instead of version detection.
try:
    from Queue import Queue   # kolejka
except ImportError:   # Py3
    from queue import Queue

Kolejkę priorytetową minimum na bazie stosu można zbudować za pomocą
uniwersalnego modułu heapq.

Py2: exec is a statement.
Py3: exec is a function.

Metaklasy
Py2: jest atrybut __metaclass__ w ciele klasy.
Py3: jest parametr metaclass przekazywany w definicji klasy.

Funkcja wbudowana reduce() z Py2 znajduje się w module functools
w Py3, ale w Py2 też jest w tym module. 
To sugeruje rozwiązanie uniwersalne:
from functools import reduce