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