Migrering til Python 3: HealthifyMe-oplevelsen.

( 18. nov 2020)

Hej !, denne blog handler om, hvordan man udfører Python 3-migrering af et ældre monolitprojekt uden at tage nedetid. Vi har to dele til dette.

Første blog: Denne blog har oplysninger om, hvorfor du skal migrere til Python 3 og sjældne tilfælde forskel i Python 2 og Python 3 og Kompatible løsninger til dem .

(Anden blog): Hvordan HealthifyMe flyttede til Python 3 uden at have nedetid med løbende udvikling.

Introduktion

Python er det primære kodningssprog på HealthifyMe. Vi bruger Python 3 til alle de nye projekter, men vores ældre projekt kørte stadig med Python 2.7 med Django (1.11). Vores monolitprojekt er 7 år gammelt med mere end 2 millioner linjer med python-kode. Nogle af grundene til, at vi flyttede til python 3:

  1. Support faldt til python 2: Efter januar 2020 ydes der ikke support til Python 2.
  2. Python 3 Adoption: Som de fleste af virksomhederne, vedtagelse af open source-projektet allerede Python 3. Nye biblioteker, værktøjer, moduler, rammer vil blive skrevet i Python 3.
    Det eksisterende open source-projekt migrerede også og ny funktion, rettelser, forbedring af sikkerheden kommer i Python 3.
  3. Softwaresikkerhed: Sikring af softwaresikkerhed er et lovkrav, især når du har at gøre med personlige oplysninger inden for GDPR. At holde din software opdateret rangerer forståeligt nok meget højt blandt de bedste sikkerhedsmetoder, og en forældet Python-tolk ville være alt andet end garanteret at vises som et rødt flag under en sikkerhedsrevision. Sikkerhedstestværktøjer som Black Duck har rapporteret om mange sårbarheder, udnyttelser og sikkerhedsproblemer i Python 2. Og de fleste af dem er rettet i de nyeste Python 3-versioner (3.7.5, 3.8.0).
  4. Ydeevne og nye funktioner : Python 3 har bedre ydeevne end python 2. Det nye softwareprodukt, der bruger python 3, har rapporteret en 12\% CPU-ydelsesforøgelse og en forbedring på 30\% i brugen af ​​hukommelsesressourcer .
    Python 3 giver os også:
    * native asynkron programmering.
    * typebetegnelser
    du kan bruge til at forbedre statisk kodeanalyse og generel brugervenlighed.
    * kædede undtagelser , som er særligt nyttige ved debugging.
    * andre nyttige funktioner , der gør kodning i Python meget mere effektiv.

Denne liste fortsætter, og den vil helt sikkert vokse med hver nye Python 3-udgivelse.

Dette er nogle grunde til, at vi migrerer til python 3. Vi har cirka 12–15 udviklerbackend-team. Dette er vores basisprojekt med 7–10 build-udgivelse dagligt med fejlrettelser, forbedringer, sikkerhedsrettelser, udvikling af nye funktioner osv. Vores største udfordring var ikke at stoppe den nuværende udviklingsproces. Vi måtte sørge for, at vores projekt er kompatibelt med Python 3.X uden at bryde kompatibiliteten med Python 2.X. Migreringen blev ledet af 1 udvikler (selvfølgelig med hjælp fra andre udviklere).

I denne artikel vil vi forsøge at illustrere alle de forskellige skridt, der er taget, problemer, og et par flere detaljer .

Forskel mellem python 2 og python 3.

Vi kan finde ud af den fælles forskel her:

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

Nu vil vi kort beskrive nogle af de sjældne og kanttilfælde, vi stod overfor, da vi startede vores migration.

Datatype sammenligning:

1. I Python 2 vil sammenligning af en integer til none fungere, således at none betragtes som mindre end et heltal, endda negative.Du kan også sammenligne none med string, string med int.
Forskellig datatypesammenligning er ikke tilladt i python 3.
Dette er kendt af de fleste af udvikleren, men vi stod over for kanttilfælde, hvor NotImplementedType blev sammenlignet med int og dette fungerer ikke i python 3.

Kodestykke:

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

Hvis vi gemmer dette med phone\_number\_validation.py og kør koden:

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

Kompatibel Løsning:
Vi er nødt til at kontrollere, om base.PHONE\_NO\_SIZE\_LIMIT er implementeret eller ej, hvis ikke, skal vi håndtere det. Ligesom:

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. Min, Max Math-funktioner:
Sammenligning kan ikke arbejde i int til none, int til str, none til str i python 3, så matematikfunktionen min og Max har også ændret sig i python 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"

Kompatibel løsning:
1. Listen skal have data af en type enten string eller int
2. Med en type, hvis none er der, kan vi have vores egen metode til at håndtere dette ligesom.

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

Hex-kodning / afkodning

Mens vi koder en streng i python 2, hvis vi følger dette .encode(‘hex’) Dette fungerer ikke i Python 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

Kompatibel løsning:
Vi skal bruge codecs til Python 3 og 2. I python 3 er input og output begge dele byte datatype.

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

Store bogstaver:

string.uppercase fungerer ikke i python 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"

Kompatibel løsning:
Brug ascii\_uppercase

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

hasattr ():

hasattr() præsenterer en enorm kantstilstand, når du skriver Python 2 og 3 kompatibel kode. hasattr() kontrollerer for eksistensen af ​​en attribut ved at prøve at hente den.

Python 2

hasattr ( objekt , navn )
Argumenterne er et objekt og en streng. Resultatet er sandt, hvis strengen er navnet på en af ​​objektets attributter, Falsk hvis ikke. (Dette implementeres ved at kalde getattr (objekt, navn) og se, om det rejser en undtagelse eller ej.)

Python 3

hasattr ( objekt , navn ) Argumenterne er et objekt og en streng. Resultatet er sandt, hvis strengen er navnet på en af ​​objektets attributter, Falsk hvis ikke. (Dette implementeres ved at kalde getattr (objekt, navn) og se, om det rejser en AttributeError eller ej.)

Find flere detaljer her: (https://medium.com/@k.wahome/python-2-vs-3-hasattr-behaviour-f1bed48b068)

Eksempelkode

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

Gem ovenstående uddrag med 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"

Kompatibel løsning:
For at gøre kodekompatibilitet i Python 2 og Python 3 er vi nødt til at ændre \_\_getattr\_\_ -funktionen som nedenfor.

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

Dikt bliver ordnet i python 3:

Fra Python 3.6+ er ordbogen nu standard indsættelse bestilt .

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

Kompatibel kode:
Ideelt set bør dette ikke bryde applikationskoden, fordi den blev ændret til ikke-ordnet til ordnet dict. Hvis vi stadig har brug for det samme resultat i begge python-versioner (rækkefølge er vigtig, testtilfælde mislykkes), skal vi bruge OrderedDict for at holde begge sprogudgange ens.

Hashing:

I python 2 kan input indtastes unicode og str, men i Python 3 har det brug for 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

Kompatibel løsning:
Python 3 har brug for bytes som input, hvor python 2 fungerer med både type unicode og 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\_\_ operatøroverbelastning:

I python 3 er \_\_div\_\_-operator synes ikke at eksistere, da den blev erstattet af \_\_truediv\_\_ helt.

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"

Kompatibel løsning:
I Python 3.x er vi nødt til at overbelaste \_\_truediv\_\_ operatorerne, ikke \_\_div\_\_ operatør. For at gøre kode kompatibel skal vi holde begge metoder som:

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

Base64-kodning:

Vi udfører base64-kodning ved hjælp af base64.b64encode(). I Python 2 kan vi sende unicode eller str som input. Men i python 3 har det brug for bytes som input.

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

Kompatibel løsning:
Vi kan have vores egen metode til base64-kodning, og den kan have input string og bytes begge dele.

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

Indbygget rund metode:

Python2 : Afrunding er udført væk fra (f.eks. er runde (0.5) 1.0 og runde (-0.5) er -1.0)
Python 3 : Afrunding udføres mod det lige valg (så for eksempel er både runde (0.5) og runde (-0.5) 0, og runde (1.5) er 2).

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

Kompatibel løsning:
Vi oprettede o din egen runde metode, der fungerer den samme som Python 2-runde i python 3 også.

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. pakkeindgangstype:

inputtype er str, Python 3 skal det være 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

Kompatibel løsning:
encode input .

listeforståelsesvariabel scope ændringer:

I Python 3 bruger listeforståelsesvariablen, der omslutter omfang, at du ikke kan få adgang til planvariablen uden for i funktionen, dette var ikke tilfældet i Python 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

Kompatibel løsning:
Vi bør undgå sådanne tilfælde. For den anden metode (two\_or\_three\_with\_method) skal vi videregive x-værdien som et argument.

math.floor og math.ceil retur datatype ændret:

I python 2 returnerer gulvet og loftet flydetatatypen, men i python 3 returnerer den int datatypen.

# 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

Kompatibel løsning:
Vi kan lav output som en svømmer i python 3. Det påvirker ikke python 2, float(floor(4.345))

Fjern markering af en python 2 syltet objekt i python 3:

Som nævnt gør vi kode kompatibel til at køre begge pythonversioner. vi stod over for et problem, mens objektet blev syltet i Python 2, men vi er ikke i stand til at fjerne markeringen i python 3. Dette kan også ske for Redis Pickled-cachede objekter.

pickle.load(), standard er at prøve at afkode alle strengdata som ASCII, og dekodningen mislykkes. Se

pickle.load() dokumentation :

Valgfri søgeordsargumenter er fix\_imports , kodning og fejl , som bruges til at kontrollere kompatibilitetsunderstøttelse af pickle stream genereret af Python 2. Hvis fix\_imports er sandt, pickle vil prøve at kortlægge de gamle Python 2-navne til de nye navne, der bruges i Python 3. kodningen og fejlene fortæller pickle, hvordan man afkoder 8-bit strenginstanser, der er syltet af Python 2; disse er som standard henholdsvis ASCII og streng. kodningen kan være bytes for at læse disse 8-bit strengforekomster som byteobjekter.

https : // stackoverflow.com / spørgsmål / 28218466 / unpickling-a-python-2-object-with-python-3

Kompatibel løsning:
Vi kan bruge nedenstående metode til at fjerne markering af objekter.

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

Tredjepartsbiblioteksrettelser:

I vores projekt bruger vi mange tredjepartspakker, mens vi opdaterer dem, stod vi over for nogle edge cases . Du kan springe dette over, hvis du ikke bruger nogen af ​​dem.

  1. Django :
    a. Django-migreringsfiler
    Når vi kører Django makemigrations i python 3, så vi nye migreringsfiler. men det samme skete ikke for python 2. Der kan være flere grunde til dette.

b præfiks: Ved makemigrering genereres de fleste af de nye filer uden at have b prefix til strengværdier. Dette skyldes, at alle strengliteraler, der bruges i dine modeller og felter (f.eks. “ verbose\_name , “ related\_name osv.), skal være konsekvent enten byte-strenge eller tekst (unicode) -strenge i både Python 2 og 3.

Kompatibel løsning: Den nemmeste måde at opnå en migration til ny migration tilføj from \_\_future\_\_ import unicode\_literal til alle modeller filer. For eksisterende migreringsfiler kører vi enten makemigration, og det skal kun ske en gang, eller så kan vi fjerne b prefix fra eksisterende migreringsfiler.

Valgfelt: I modeller bruger vi dict.items (). Som vi ved nu, at dict bliver ordnet i python 3 , så værdierne returneres fra dict.items () vil være forskellige i Python 2 og Python 3.

Kompatibel løsning: For at gøre kompatible for begge sorterede vi (dict. items ()) og genereret migrationsfil, der nu er kompatibel til begge pythonversioner.

b. “ Object” vises i administrationskonsol
For python 3 i administrationskonsol kan vi se Objekt som feltværdi i stedet for en streng. hvis der skete, fordi vores modelklasse har metode.

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

Vi kan have str-metoden \_\_str\_\_, og den fungerer for både Python 2 og Python 3. Men den mislykkes, hvis str-versionen har ikke-ASCII-tegn.

Kompatibel Løsning: Fik løsningen fra her , tilføjede @python\_2\_unicode\_compatible dekoratør til modeller og modificerede \_\_unicode\_\_ til \_\_str\_\_.

c . Django-forespørgselsobjektudskæring
Django-forespørgselsobjekt har kapacitet til at hente poster. til Django version (1.11), python 2 understøtter den skæring til int og str. I Python 3 understøtter det kun skæring gennem intet.

# 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]: ]>

Kompatibel løsning: Undgå strengskæring, der alligevel ikke er en god tilgang.

2. Redis : Redis er almindeligt anvendt python-pakke. redis-py 3.0 introducerer mange nye funktioner, men krævede, at der skal foretages flere bagudkompatible ændringer i processen.

Kompatibel løsning:
https://pypi.org/project/redis/ herfra kan vi finde ud af ændringerne, og hvordan vi gør kompatible kode. Vi lavede vores egne Redis-metoder, der er kompatible med begge Redis-versioner. Ligesom

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 er en smart app, der understøtter automatisk eller manuel cache-forespørgsel og automatisk granuleret hændelsesdrevet ugyldighed. Der er en gotcha, mens Django cacheops gemmer værdier til Redis, det gør dem til et pickle-objekt.

I python 2 er der 3 forskellige protokoller (0, 1, 2) og standard er 0.I python 3 er der 5 forskellige protokoller (0, 1, 2, 3, 4) og standard er 3.

Pickle bruger standard pickle-protokollen til at dumpe data. Så i python 3, hvis vi laver et pickle-objekt og ønsker at fjerne markeringen i Python 2, fungerer ikke, fordi pickle-protokol 3 ikke er tilgængelig i python 2.

Kompatibel løsning:
Vi kan specificere protokolparameteren, når vi påkalder pickle.dump.
django-cacheops har ikke en mulighed for at levere pickle-protokollen. Vi brugte abepatching til at ordne dette.

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

Også som nævnt ovenfor, hvordan man Fjern markeringen af ​​en python 2 syltet objekt i python 3 . Vi ønsker at få data i python 3, vi kan stå over for UnicodeDecodeError på grund af plukning udført i forskellige pythonversioner.
dette er også sorteret ved hjælp af patching

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 : vi har en metode til sletning af nøgler baseret på mønsteret. I Python 2 bruger vi versionen

1.6.5 nøglesøgningen / sletningen foregik uden scanning, men for python 3 opdaterede vi versionen til

2.1 hvor mønstersøgningen sker ved hjælp af Redis-scanning, det gør det så langsomt. Dette forårsagede problemet. Git hub-problem til dette .

Kompatibel løsning:
Vi sorterede problemet ved hjælp af den gamle måde at slette mønstre på. i stedet for at ringe til cache.delete\_pattern(pattern) laver vi

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

Hvad er det næste

I (del to ) på denne blog, hvor vi vil undersøge, hvordan vi flytter til python 3 uden at have nedetid med den løbende udvikling.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *