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