Migrating to Python 3: The HealthifyMe Experience. (Polski)

( 18 listopada 2020 r.)

Witaj !, ten blog opowiada o tym, jak przeprowadzić migrację do Pythona 3 starszego projektu monolitycznego bez żadnych przestojów. Mamy na to dwie części.

Pierwszy blog: ten blog zawiera informacje o tym, dlaczego należy przeprowadzić migrację do Pythona 3 oraz rzadkie różnice w wielkości liter w Pythonie 2 i Pythonie 3 oraz Kompatybilne rozwiązania dla tych .

(Drugi blog): Jak HealthifyMe przeniosło się na Python 3 bez przestojów związanych z ciągłym rozwojem.

Wprowadzenie

Python jest podstawowym językiem programowania w HealthifyMe. Używamy Pythona 3 we wszystkich nowych projektach, ale nasz starszy projekt nadal działał z Pythonem 2.7 i Django (1.11). Nasz projekt monolit ma 7 lat i zawiera ponad 2 miliony linii kodu Pythona. Niektóre z powodów, dla których przenieśliśmy się na python 3:

  1. Usunięto obsługę Pythona 2: Po styczniu 2020 r. Obsługa Pythona 2 nie będzie dostępna.
  2. Python 3 Adoption: Jak większość firm, projekt open-source już przyjmuje Python 3. Nowe biblioteki, narzędzia, moduły, frameworki zostaną napisane w Pythonie 3.
    Istniejący projekt open source również został zmigrowany i nowa funkcja, poprawki, poprawa bezpieczeństwa pojawią się w Pythonie 3.
  3. Bezpieczeństwo oprogramowania: Zapewnienie bezpieczeństwa oprogramowania jest wymogiem prawnym, zwłaszcza gdy masz do czynienia z danymi osobowymi w zakresie RODO. Dbanie o aktualność oprogramowania, co zrozumiałe, zajmuje bardzo wysokie miejsce wśród sprawdzonych metod zabezpieczeń, i przestarzały interpreter języka Python to wszystko, ale gwarantowane pojawiają się jako czerwona flaga podczas audytu bezpieczeństwa. Narzędzia do testowania zabezpieczeń, takie jak Black Duck, zgłosiły wiele luk w zabezpieczeniach, exploitów i problemów z bezpieczeństwem w Pythonie 2. Większość z nich została naprawiona w najnowszych wersjach Pythona 3 (3.7.5, 3.8.0).
  4. Wydajność i nowe funkcje : Python 3 ma lepszą wydajność niż Python 2. Nowe oprogramowanie korzystające z języka Python 3 zgłosiło 12\% wzrost wydajności procesora i 30\% poprawę wykorzystania zasobów pamięci .
    również, Python 3 daje nam:
    * natywne programowanie asynchroniczne.
    * wpisz adnotacje
    , których możesz użyć, aby poprawić statyczną analizę kodu i ogólną użyteczność.
    * połączone wyjątki , które są szczególnie przydatne podczas debugowania.
    * inne pomocne funkcje , dzięki którym kodowanie w Pythonie jest znacznie wydajniejsze.

Ta lista jest długa i na pewno się powiększy z każdym nowym wydaniem Pythona 3.

Oto kilka powodów, dla których przechodzimy do Pythona 3. Mamy około 12-15 zespołów programistów. To jest nasz podstawowy projekt, w którym codziennie pojawia się 7–10 wersji, z poprawkami błędów, ulepszeniami, poprawkami bezpieczeństwa, rozwojem nowych funkcji itp. Naszym głównym wyzwaniem było nie zatrzymywanie obecnego procesu rozwoju. Musieliśmy się upewnić, że nasz projekt jest kompatybilny z Pythonem 3.X bez przerywania kompatybilności z Pythonem 2.X. Migracja była prowadzona przez 1 programistę (oczywiście z pomocą innych programistów).

W tym artykule postaramy się zilustrować wszystkie podjęte kroki, napotkane problemy i kilka innych szczegółów .

Różnica między Pythonem 2 a Pythonem 3.

Typową różnicę możemy znaleźć tutaj:

https://docs.python.org/ 3 / whatsnew / 3.0.html
https://sebastianraschka.com/Articles/2014\_python\_2\_3\_key\_diff.html
https://jaxenter.com/differences-python-2-3-148432.html
https: // python-future.org/compatible\_idioms.html

Teraz opiszemy pokrótce niektóre rzadkie i skrajne przypadki, napotkaliśmy, gdy rozpoczęliśmy migrację.

Porównanie typów danych:

1. W Pythonie 2 porównanie integer z none będzie działać tak, że none będzie uważane za mniej niż liczba całkowita, nawet ujemna.Możesz także porównać none z string, string z int.
Porównanie różnych typów danych nie jest dozwolone w Pythonie 3.
Jest to znane większości programistów, ale napotkaliśmy skrajny przypadek, w którym NotImplementedType zostało porównane z int i to nie będzie działać w Pythonie 3.

Fragment kodu:

class Base(object):
PHONE\_NO\_SIZE\_LIMIT = NotImplementedbase = Base()
if base.PHONE\_NO\_SIZE\_LIMIT > 10:
print("Pass correct phone number")
else:
print("Valid phone number")

Jeśli to zapiszemy z phone\_number\_validation.py i uruchom kod:

# Python 2
[email protected] ~ \% python phone\_number\_validation.py
Pass correct phone number# Python 3
[email protected] ~ \% python3.7 phone\_number\_validation.py
Traceback (most recent call last):
File "phone\_number\_validation.py", line 4, in
if base.PHONE\_NO\_SIZE\_LIMIT > 10:
TypeError: ">" not supported between instances of "NotImplementedType" and "int"

Zgodny Rozwiązanie:
Musimy sprawdzić, czy base.PHONE\_NO\_SIZE\_LIMIT jest zaimplementowany, czy nie, jeśli nie, to musimy sobie z tym poradzić. Na przykład:

if isinstance(base.PHONE\_NO\_SIZE\_LIMIT, type(NotImplemented)):
# Have logic here, also exit/return here.
print("Phone size is not implemented")if base.PHONE\_NO\_SIZE\_LIMIT > 10:
print("Pass correct phone number")
else:
print("Valid phone number")

2. Funkcje matematyczne Min, Max:
Porównanie nie działa w int z none, int to str, none na str w Pythonie 3, więc funkcje matematyczne min i Max również uległy zmianie w Pythonie 3.

# Python 2 
>>> max([1, None, 2])
2
>>> max([1, None, 2, "abc"])
"abc"
>>> min([1, None, 2, "abc"])
None # Python 3
>>> max([1, None, 2])
Traceback (most recent call last):
File "", line 1, in
TypeError: ">" not supported between instances of "NoneType" and "int" >>> max([1, 2, "abc", None])
Traceback (most recent call last):
File "", line 1, in TypeError: ">" not supported between instances of "str" and "int"

Zgodne rozwiązanie:
1. Lista powinna zawierać dane jednego typu string lub int
2. Z jednym typem, jeśli none czy istnieje nasza własna metoda, aby sobie z tym poradzić.

def py2max(input\_list):
"""Get the maximum item from list."""
if not input\_list:
raise ValueError("List should not be empty")
formated\_input\_list = [rec for rec in input\_list if rec is not None]
return max(formated\_input\_list) if formated\_input\_list else None

Kodowanie / dekodowanie szesnastkowe

Podczas gdy kodujemy ciąg w pythonie 2, jeśli postępujemy zgodnie z tym .encode(‘hex’) To nie będzie działać w Pythonie 3

# ENCODING# Python 2
>>> "msg\_to\_be\_encoded".encode("hex")
"6d73675f746f5f62655f656e636f646564"# Python 3
>>> "msg\_to\_be\_encoded".encode("hex")
Traceback (most recent call last):
File "", line 1, in
LookupError: "hex" is not a text encoding; use codecs.encode() to handle arbitrary codecs# DECODING# Python 2
>>> "6d73675f746f5f62655f656e636f646564".decode("hex") "msg\_to\_be\_encoded"# Python 3
>>> b"6d73675f746f5f62655f656e636f646564".decode("hex")
Traceback (most recent call last):
File "", line 1, in
LookupError: "hex" is not a text encoding; use codecs.decode() to handle arbitrary codecs# Also look at exception here in python 2 and python 3.# Python 3>>> b"6d73675f746f5f62655f656e636f646564".decode("hex")
Traceback (most recent call last):
File "", line 1, in
LookupError: "hex" is not a text encoding; use codecs.decode() to handle arbitrary codecs# Python 2>>> "6d73675f746f5f62655f656e636f64656".decode("hex")
Traceback (most recent call last):
File "", line 1, in
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/hex\_codec.py", line 42, in hex\_decode
output = binascii.a2b\_hex(input)
TypeError: Odd-length string

Zgodne rozwiązanie:
Powinniśmy używać codecs dla Pythona 3 i 2. W Pythonie 3 dane wejściowe i wyjściowe to byte typ danych.

# Python 2
>>> import codecs
>>> message = "msg\_to\_be\_encoded"
>>> codecs.encode(message.encode(), "hex")
"6d73675f746f5f62655f656e636f646564"# Python 3
>>> message = "msg\_to\_be\_encoded"
>>> codecs.encode(message.encode(), "hex")
b"6d73675f746f5f62655f656e636f646564"

Wielkie litery:

string.uppercase nie działa w Pythonie 3.

# Python 2 
>>> import string
>>> string.uppercase
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"# Python 3
>>> import string
>>> string.uppercase
Traceback (most recent call last):
File "", line 1, in
AttributeError: module "string" has no attribute "uppercase"

Zgodne rozwiązanie:
Użyj ascii\_uppercase

# Python 2
>>> import string
>>> string.ascii\_uppercase
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"# Python 3
>>> import string
>>> string.ascii\_uppercase
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"

hasattr ():

hasattr() przedstawia ogromną przewagę podczas pisania w Pythonie 2 i 3 zgodny kod. hasattr() sprawdza istnienie atrybutu, próbując go pobrać.

Python 2

hasattr ( obiekt , nazwa )
Argumentami są obiekt i ciąg. Wynik to True, jeśli ciąg jest nazwą jednego z atrybutów obiektu, lub False, jeśli nie. (Jest to realizowane przez wywołanie getattr (obiekt, nazwa) i sprawdzenie, czy zgłasza wyjątek, czy nie.)

Python 3

hasattr ( obiekt , nazwa ) Argumentami są obiekt i ciąg. Wynik to True, jeśli ciąg jest nazwą jednego z atrybutów obiektu, lub False, jeśli nie. (Jest to realizowane przez wywołanie getattr (obiekt, nazwa) i sprawdzenie, czy wywołuje AttributeError , czy nie.)

Więcej szczegółów znajdziesz tutaj: (https://medium.com/@k.wahome/python-2-vs-3-hasattr-behaviour-f1bed48b068)

Przykładowy kod

class Foo(dict): 
def \_\_init\_\_(self):
super(Foo, self).\_\_init\_\_()
self.example\_dict = {}

def \_\_getitem\_\_(self, key):
try:
return super(Foo, self).\_\_getitem\_\_(key)
except KeyError:
return self.example\_dict[key] def \_\_getattr\_\_(self, key):
return self[key]foo = Foo()
if hasattr(foo, "not\_present\_key"):
pass
else:
print("Not Found")

Zapisz powyższy fragment z hasattr\_test.py

# Python 2[email protected] ~ \% python hasattr\_test.py 
Not Found# Python 3[email protected] ~ \% python3.7 hasattr\_test.py
Traceback (most recent call last):
File "hasattr\_test.py", line 8, in \_\_getitem\_\_
return super(Foo, self).\_\_getitem\_\_(key)
KeyError: "not\_present\_key"During handling of the above exception, another exception occurred:Traceback (most recent call last):
File "hasattr\_test.py", line 17, in
if hasattr(foo, "not\_present\_key"):
File "hasattr\_test.py", line 13, in \_\_getattr\_\_
return self[key]
File "hasattr\_test.py", line 10, in \_\_getitem\_\_
return self.example\_dict[key]
KeyError: "not\_present\_key"

Zgodne rozwiązanie:
Aby zapewnić zgodność kodu w Pythonie 2 i Pythonie 3, musimy zmienić funkcję \_\_getattr\_\_ jak poniżej.

def \_\_getattr\_\_(self, key):
try:
return self[key]
except KeyError:
raise AttributeError

Dykt zostaje uporządkowany w Pythonie 3:

Od Pythona 3.6 i nowszych słownik jest teraz domyślny uporządkowane wstawienie .

# Python 2
>>> sample\_dict = {}
>>> sample\_dict["a"] = 1
>>> sample\_dict["b"] = 2
>>> sample\_dict["c"] = 3
>>> sample\_dict["d"] = 4
>>> sample\_dict
{"a": 1, "c": 3, "b": 2, "d": 4}# Python 3
>>> sample\_dict = {}
>>> sample\_dict["a"] = 1
>>> sample\_dict["b"] = 2
>>> sample\_dict["c"] = 3
>>> sample\_dict["d"] = 4
>>> sample\_dict
{"a": 1, "b": 2, "c": 3, "d": 4}

Zgodny kod:
W idealnym przypadku nie powinno to zepsuć kodu aplikacji, ponieważ zmieniło się na nieuporządkowany na uporządkowany dykt. Jeśli nadal potrzebujemy tego samego wyniku w obu wersjach Pythona (kolejność jest ważna, przypadek testowy kończy się niepowodzeniem), musimy użyć OrderedDict, aby zachować te same wyniki w obu językach.

Hashing:

W pythonie 2 można wpisać unicode i str, ale w Pythonie 3 wymaga bytes

# Python 2
>>> import hashlib
>>> message = "healthify"
>>> hashlib.sha512(message).hexdigest().lower()
"f910c1fa68087a546512ac3b175c99ee7eba21360fa4e579c2aed649c7e4a43466c56bceedcd60d783bc6e7d069a16f0b9c67140d6c129d2a1898af8cfb62719"# Python 3
>>> message = "healthify"
>>> hashlib.sha512(message).hexdigest().lower()
Traceback (most recent call last):
File "", line 1, in
TypeError: Unicode-objects must be encoded before hashing

Zgodne rozwiązanie:
Python 3 potrzebuje bytes jako dane wejściowe, gdzie python 2 działa z typami unicode i str.

# Python 2
>>> import hashlib
>>> message = "healthify"
>>> hashlib.sha512(message.encode("utf-8")).hexdigest().lower()
"f910c1fa68087a546512ac3b175c99ee7eba21360fa4e579c2aed649c7e4a43466c56bceedcd60d783bc6e7d069a16f0b9c67140d6c129d2a1898af8cfb62719"# Python 3
>>> message = "healthify"
>>> hashlib.sha512(message.encode("utf-8")).hexdigest().lower()
"f910c1fa68087a546512ac3b175c99ee7eba21360fa4e579c2aed649c7e4a43466c56bceedcd60d783bc6e7d069a16f0b9c67140d6c129d2a1898af8cfb62719"

\_\_div\_\_ przeciążenie operatora:

W Pythonie 3, Wydaje się, że operator \_\_div\_\_ nie istnieje, ponieważ został całkowicie zastąpiony przez \_\_truediv\_\_.

class item:
fats = 0.0

def \_\_div\_\_(self, other):
self.fats = self.fats / otherit = item()
it.fats = 34.0
it / 3
print(it.fats)# python 2 output
11.3333333333# Python 3 output
Traceback (most recent call last):
File "div\_overloading.py", line 16, in
print(AB / 3)
TypeError: unsupported operand type(s) for /: "Vector2" and "int"

Zgodne rozwiązanie:
W Pythonie 3.x musimy przeciążać operatory \_\_truediv\_\_, a nie \_\_div\_\_. Aby kod był zgodny, musimy zachować obie metody, takie jak:

class item:
fats = 0.0 def \_\_div\_\_(self, other):
self.fats = self.fats / other

def \_\_truediv\_\_(self, other):
self.fats = self.fats / otherit = item()
it.fats = 34.0
it / 3
print(it.fats)# python 2 output
11.3333333333# Python 3 output
11.333333333333334

Kodowanie Base64:

Wykonujemy kodowanie base64 za pomocą base64.b64encode(). W Pythonie 2 możemy przekazać unicode lub str jako dane wejściowe. Ale w Pythonie 3 potrzebuje bytes jako danych wejściowych.

# Python 2
>>> from base64 import b64encode
>>> b64encode("man")
"bWFu"
>>> b64encode(u"man")
"bWFu"# Python 3
>>> from base64 import b64encode
>>> b64encode("man")
Traceback (most recent call last):
File "", line 1, in
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/base64.py", line 58, in b64encode
encoded = binascii.b2a\_base64(s, newline=False)
TypeError: a bytes-like object is required, not "str"

Zgodne rozwiązanie:
Możemy mieć własną metodę kodowania base64 i może ona mieć dane wejściowe string i bytes oba.

import base64
import six
def base64ify(bytes\_or\_str):
if six.PY3 and isinstance(bytes\_or\_str, str):
input\_bytes = bytes\_or\_str.encode("utf8")
else:
input\_bytes = bytes\_or\_str
try:
output\_bytes = base64.b64encode(input\_bytes)
except (UnicodeEncodeError, TypeError):
# This happens when the input message has
# non-ascii encodable characters in an unicode string
# `"`(ascii encodable) vs `’`(non-ascii encodable)
# In this case, we first need to encode it to utf-8
# and then do the base64 encoding
output\_bytes = base64.b64encode(input\_bytes.encode("utf-8"))
if six.PY3:
return output\_bytes.decode("ascii")
else:
return output\_bytes

Wbudowana metoda rundy:

Python2 : Zaokrąglanie zostało zakończone z dala od (na przykład zaokrąglenie (0,5) to 1,0, a zaokrąglenie (-0,5) to -1,0)
Python 3 : Zaokrąglanie zostało zakończone w kierunku równego wyboru (na przykład zarówno okrągłe (0,5), jak i okrągłe (-0,5) to 0, a okrągłe (1,5) to 2).

# Python 2
>>> round(15.5)
16.0
>>> round(16.5)
17.0# Python 3
>>> round(15.5)
16
>>> round(16.5)
16

Zgodne rozwiązanie:
Stworzyliśmy o Twoja własna metoda rundy, która działa tak samo jak runda Pythona 2 również w Pythonie 3.

def py2\_round(x, d=0):
"""Round same as PY2 in PY3."""
p = 10 ** d
if x >= 0:
return float(math.floor((x * p) + 0.5)) / p
else:
return float(math.ceil((x * p) - 0.5)) / p

struct. typ danych wejściowych pakietu:

typ danych wejściowych to str, w Pythonie 3 powinno to być byte

# Python 2
>>> import struct
>>> import struct
>>> string = "blab"
>>> s = struct.Struct(b"4s")
>>> packed\_data = s.pack(string)# Python 3
>>> import struct
>>> string = "blab"
>>> s = struct.Struct(b"4s")
>>> packed\_data = s.pack(string)
Traceback (most recent call last):
File "", line 1, in
struct.error: argument for "s" must be a bytes object

Zgodne rozwiązanie:
encode dane wejściowe .

zmienna rozumienia listy scope changes:

W Pythonie 3 zmienna rozumienia listy używa zakresu obejmującego, co oznacza, że ​​nie będzie można uzyskać dostępu do zmiennej planu poza funkcją, to było inaczej w Pythonie 2.

# Python 2
>>> def two\_or\_three():
... x = 3
... [0 for x in range(3)]
... return x
...
>>> two\_or\_three()
2>>> def two\_or\_three\_with\_method():
... def print\_number():
... print(x)
... [0 for x in range(3)]
... print\_number()
...
>>> two\_or\_three\_with\_method()
2# Python 3
>>> def two\_or\_three():
... x = 3
... [0 for x in range(3)]
... return x
...
>>> two\_or\_three()
3>>> def two\_or\_three\_with\_method():
... def print\_number():
... print(x)
... [0 for x in range(3)]
... print\_number()
...
>>> two\_or\_three\_with\_method()
Traceback (most recent call last):
File "", line 1, in
File "", line 5, in two\_or\_three
File "", line 3, in print\_number
NameError: name "x" is not defined

Zgodne rozwiązanie:
Powinniśmy unikać takich przypadków. W przypadku drugiej metody (two\_or\_three\_with\_method) musimy przekazać wartość x jako argument.

math.floor i zmieniono typ danych zwracanych przez math.ceil:

W Pythonie 2 podłoga i sufit zwracają typ danych zmiennoprzecinkowych, ale w Pythonie 3 zwraca typ danych int.

# Python 2
>>> from math import floor,ceil
>>> floor(4.345)
4.0
>>> ceil(4.345)
5.0# Python 3
>>> from math import floor,ceil
>>> floor(4.345)
4
>>> ceil(4.345)
5

Zgodne rozwiązanie:
Możemy ustaw dane wyjściowe jako zmiennoprzecinkowe w pythonie 3. Nie wpłynie to na Python 2, float(floor(4.345))

Usuwanie wybrania python 2 piklowany obiekt do pythona 3:

Jak już wspomnieliśmy, zapewniamy kompatybilność kodu do uruchamiania obu wersji Pythona. napotkaliśmy problem, gdy obiekt jest marynowany w Pythonie 2, ale nie możemy go rozpakować w Pythonie 3. Może się to również zdarzyć w przypadku obiektów buforowanych Redis Pickled.

pickle.load(), domyślnie próbuje zdekodować wszystkie dane łańcuchowe jako ASCII, a dekodowanie kończy się niepowodzeniem. Zobacz

pickle.load() dokumentację :

Opcjonalne argumenty słów kluczowych to fix\_imports , kodowanie i błędy , które są używane do kontrolowania obsługi zgodności dla strumienia pikle generowanego przez Python 2. Jeśli fix\_imports ma wartość true, pickle spróbuje odwzorować stare nazwy Pythona 2 na nowe nazwy używane w Pythonie 3. kodowanie i błędy mówią pickle jak dekodować 8-bitowe instancje ciągów marynowane przez Pythona 2; te domyślnie mają odpowiednio „ASCII” i „ścisłe”. Kodowanie może mieć wartość „bajtów”, aby odczytać te 8-bitowe instancje ciągu jako obiekty bajtów.

https ://przepełnienie stosu.com / questions / 28218466 / unpickling-a-python-2-object-with-python-3

Zgodne rozwiązanie:
Aby odblokować obiekty, możemy użyć poniższej metody.

def unpickling\_py2\_to\_py3(pickled\_value):
"""Unpickling python 2 pickled in to python 3."""
if isPY3():
try:
value = pickle.loads(pickled\_value)
except UnicodeDecodeError:
value = pickle.loads(pickled\_value, encoding="latin1")
else:
value = pickle.loads(pickled\_value)
return value

Poprawki w bibliotekach innych firm:

W naszym projekcie używamy wielu pakietów innych firm, podczas ich aktualizacji napotkaliśmy kilka skrajnych przypadków . Możesz to pominąć, jeśli nie używasz żadnego z nich.

  1. Django :
    a. Pliki migracji Django
    Kiedy uruchamiamy Django makemigrations w Pythonie 3, widzieliśmy nowe pliki migracji. ale to samo nie działo się w przypadku Pythona 2. Może być wiele przyczyn takiego stanu rzeczy.

b prefiks: Podczas makemigracji większość nowych plików jest generowana bez b prefix dla wartości ciągów. Dzieje się tak, ponieważ wszystkie literały ciągów używane w modelach i polach (np. „ verbose\_name , „ related\_name itd.), musi być spójnymi ciągami bajtów lub tekstów (Unicode) zarówno w Pythonie 2, jak i 3.

Zgodne rozwiązanie: Najłatwiejszym sposobem przeprowadzenia jednej migracji dla nowej migracji jest dodanie from \_\_future\_\_ import unicode\_literal do wszystkich plików modeli. W przypadku istniejących plików migracji albo uruchomimy makemigration i powinno to nastąpić tylko raz, albo możemy usunąć b prefix z istniejących plików migracji.

Pole wyboru: W modelach używamy dict.items (). Jak wiemy teraz, że dykt został uporządkowany w pythonie 3 , więc wartości zwracane z dict.items () będą się różnić Python 2 i Python 3.

Zgodne rozwiązanie: Aby zapewnić zgodność dla obu, posortowaliśmy (dict. items ()) i wygenerowany plik migracji, który jest teraz zgodny z obydwoma wersjami Pythona.

b. Wyświetlanie „ Object” w konsoli administracyjnej
W przypadku języka Python 3 w konsoli administracyjnej możemy zobaczyć Object jako wartość pól zamiast ciągu. jeśli miało to miejsce, ponieważ nasza klasa modelu ma metodę.

def \_\_unicode\_\_(self):
return "MyModel: {}".format(self.name)

Możemy mieć metodę str \_\_str\_\_, która działa zarówno dla Pythona 2, jak i Pythona 3. Ale zakończy się niepowodzeniem, jeśli wersja str zawiera znaki inne niż ASCII.

Zgodne Rozwiązanie: Rozwiązanie zostało znalezione tutaj , dodano dekorator @python\_2\_unicode\_compatible dla modeli i zmodyfikowanych \_\_unicode\_\_ do \_\_str\_\_.

c . Cięcie obiektu zapytania Django
Obiekt zapytania Django ma możliwość cięcia rekordów w celu pobrania rekordów. dla wersji Django (1.11), python 2 obsługuje krojenie dla int i str. W Pythonie 3 obsługuje tylko przekroje przez int.

# Python 2
>>> from food import models
>>> foods = models.foods.objects.all()
>>> foods[1:2] # int slicing
]>
>>> foods["1":"2"] # string slicing
]># Python 3
In [2]: from food import models
In [3]: foods = models.Foods.objects.all()
In [4]: foods["1":"2"]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in
----> 1 foods["1":"2"]~/Venvs/py3\_venv/lib/python3.7/site-packages/django/db/models/query.py in \_\_getitem\_\_(self, k)
265 raise TypeError
266 assert ((not isinstance(k, slice) and (k >= 0)) or
--> 267 (isinstance(k, slice) and (k.start is None or k.start >= 0) and
268 (k.stop is None or k.stop >= 0))), \
269 "Negative indexing is not supported."TypeError: ">=" not supported between instances of "str" and "int"In [5]: foods[1:2]
Out[5]: ]>

Zgodne rozwiązanie: Unikaj dzielenia ciągów, które i tak nie jest dobrym podejściem.

2. Redis : Redis jest często używanym pakietem Pythona. redis-py 3.0 wprowadza wiele nowych funkcji, ale wymagało wprowadzenia w tym procesie kilku niekompatybilnych wstecz zmian.

Zgodne rozwiązanie:
https://pypi.org/project/redis/ stąd możemy dowiedzieć się o zmianach i jak zapewnić zgodność kod. Stworzyliśmy własne metody Redis, które są zgodne z obiema wersjami Redis. Na przykład

import six
def redis\_zadd(redis\_connection, key, **values):
"""Redis method zadd for python 2 and python 3 compatibility."""
if six.PY3:
redis\_connection.zadd(key, values)
else:
redis\_connection.zadd(key, **values)def redis\_zincrby(redis\_connection, key, value, score):
"""Redis method zincrby for python 2 and python 3 compatibility."""
if six.PY3:
redis\_connection.zincrby(key, score, value)
else:
redis\_connection.zincrby(key, value, score)

3. django-cacheops : Cacheops to zgrabna aplikacja, obsługuje automatyczne lub ręczne buforowanie zestawu zapytań i automatyczne, szczegółowe unieważnianie na podstawie zdarzeń. Problem polega na tym, że pamięć podręczna Django przechowuje wartości w Redis, co czyni je obiektem piklowania.

W Pythonie 2 są 3 różne protokoły (0, 1, 2), a domyślnym jest 0. W Pythonie 3 jest 5 różnych protokołów (0, 1, 2, 3, 4), a domyślnym jest 3.

Pickle używa domyślnego protokołu pickle do zrzucania danych. python 3, jeśli utworzymy obiekt typu pikle i chcemy go rozpakować w Pythonie 2, nie będzie działać, ponieważ protokół pikle 3 nie jest dostępny w Pythonie 2.

Zgodne rozwiązanie:
Możemy określić parametr protokołu podczas wywoływania pickle.dump.
django-cacheops nie mają możliwość dostarczenia protokołu trawienia. Użyliśmy małpiego poprawiania, aby to rozwiązać.

import cacheops
from cacheops.cross import pickle
@cacheops.redis.handle\_connection\_failure
def
\_custom\_cacheops\_redis\_set(self, cache\_key, data, timeout=None):
pickled\_data = pickle.dumps(data, 2) # Protocol 2 is valid in both Python version.
if timeout is not None:
self.conn.setex(cache\_key, timeout, pickled\_data)
else:
self.conn.set(cache\_key, pickled\_data)cacheops.RedisCache.set = \_custom\_cacheops\_redis\_set

Jak wspomniano powyżej, jak Rozpakować Pythona 2 piklowany obiekt do Pythona 3 . Chcemy uzyskać dane w Pythonie 3, możemy stawić czoła UnicodeDecodeError z powodu wybierania dokonanego w różnych wersjach Pythona.
jest to również sortowane za pomocą łatania

import six
from cacheops.simple import CacheMiss
if six.PY3:
import pickle
else:
import cPickle as pickledef unpickling\_py2\_to\_py3(pickled\_value):
"""Unpickling python 2 pickled in to python 3."""
if six.PY3:
try:
value = pickle.loads(pickled\_value)
except UnicodeDecodeError:
value = pickle.loads(pickled\_value, encoding="latin1")
else:
value = pickle.loads(pickled\_value)
return valuedef
\_custom\_cacheops\_redis\_get(self, cache\_key):
data = self.conn.get(cache\_key)
if data is None:
raise CacheMiss
return unpickling\_py2\_to\_py3(data)cacheops.RedisCache.get = \_custom\_cacheops\_redis\_get

4. django-redis-cache : mamy metodę do usuwania kluczy na podstawie wzorca. W Pythonie 2 używamy wersji

1.6.5, w którym wyszukiwanie / usuwanie kluczy odbywało się bez skanowania, a dla Pythona 3 zaktualizowaliśmy wersję do

2.1 gdzie wyszukiwanie wzorców odbywa się za pomocą skanowania Redis, to czyni je tak powolnym. To powodowało problem. Problem z centrum Git w tym przypadku .

Zgodne rozwiązanie:
Rozwiązaliśmy problem przy użyciu starego sposobu usuwania wzorców. zamiast dzwonić do cache.delete\_pattern(pattern) robimy

pattern = cache.make\_key(pattern)
keys = cache.master\_client.keys(pattern)
if len(keys):
cache.master\_client.delete(*keys)

Co dalej

Za (część druga ) tego bloga, , gdzie dowiemy się, jak przejść na Python 3 bez przestojów związanych z ciągłym rozwojem.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *