Migreren naar Python 3: The HealthifyMe Experience.

( 18 nov.2020)

Hallo !, deze blog gaat over het uitvoeren van Python 3-migratie van een legacy monolietproject zonder enige downtime. We hebben hiervoor 2 delen.

Eerste blog: deze blog bevat informatie over waarom je naar Python 3 zou moeten migreren en een zeldzaam gevalverschil in Python 2 en Python 3 en Compatibele oplossingen voor degenen .

(Tweede blog): Hoe HealthifyMe naar Python 3 verhuisde zonder downtime met voortdurende ontwikkeling.

Inleiding

Python is de primaire codeertaal bij HealthifyMe. We gebruiken Python 3 voor alle nieuwe projecten, maar ons oude project draaide nog met Python 2.7 met Django (1.11). Ons monolietproject is 7 jaar oud met meer dan 2 miljoen regels pythoncode. Enkele van de redenen waarom we naar python 3 zijn verhuisd:

  1. Ondersteuning voor python 2 gedaald: Na januari 2020 wordt geen ondersteuning voor Python 2 geboden.
  2. Python 3-adoptie: Zoals van de meeste bedrijven, het open-sourceproject past Python 3 al toe. Nieuwe bibliotheken, tools, modules, frameworks zullen in Python 3 worden geschreven.
    Het bestaande open-sourceproject is ook gemigreerd en nieuwe functie, fixes, beveiligingsverbetering komen in Python 3.
  3. Softwarebeveiliging: Zorgen voor softwarebeveiliging is een wettelijke vereiste, vooral wanneer je hebt te maken met persoonlijke informatie op het gebied van GDPR. Het up-to-date houden van uw software scoort begrijpelijkerwijs zeer hoog onder de best practices op het gebied van beveiliging, en een verouderde Python-interpreter zou vrijwel gegarandeerd zijn verschijnen als een rode vlag tijdens een beveiligingsaudit. Beveiligingstesttools zoals Black Duck hebben veel kwetsbaarheden, exploits en beveiligingsproblemen gerapporteerd in Python 2. En de meeste zijn opgelost in de nieuwste Python 3-versies (3.7.5, 3.8.0).
  4. Prestaties en nieuwe functies : Python 3 heeft betere prestaties dan Python 2. Het nieuwe softwareproduct dat Python 3 gebruikt, heeft een verbetering van de CPU-prestaties van 12\% gerapporteerd en een verbetering van 30\% in het gebruik van geheugenbronnen .
    ook, Python 3 geeft ons:
    * native asynchrone programmering.
    * type annotaties
    die u kunt gebruiken om de analyse van statische code en de algehele bruikbaarheid te verbeteren.
    * gekoppelde uitzonderingen , die vooral handig zijn bij het opsporen van fouten.
    * andere handige functies die codering in Python veel efficiënter maken.

Deze lijst gaat maar door, en hij zal zeker groeien bij elke nieuwe uitgave van Python 3.

Dit zijn enkele redenen voor ons om naar Python 3 te migreren. We hebben ongeveer 12-15 ontwikkelaars backend-team. Dit is ons basisproject met 7–10 build-releases per dag, met bugfixes, verbeteringen, beveiligingsreparaties, ontwikkeling van nieuwe functies enz. Onze belangrijkste uitdaging was niet om het huidige ontwikkelingsproces te stoppen. We moesten ervoor zorgen dat ons project compatibel is met Python 3.X zonder de compatibiliteit met Python 2.X te verbreken. De migratie werd geleid door 1 ontwikkelaar (uiteraard met de hulp van andere ontwikkelaars).

In dit artikel zullen we proberen alle verschillende stappen die zijn genomen, de problemen waarmee we te maken hebben en nog een paar details te illustreren .

Verschil tussen python 2 en python 3.

We kunnen hier het algemene verschil vinden:

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 zullen we in het kort enkele van de zeldzame en randgevallen beschrijven die we geconfronteerd toen we onze migratie begonnen.

Vergelijking van gegevenstypen:

1. In Python 2 zal het vergelijken van een integer met none werken, zodat none als minder wordt beschouwd dan een geheel getal, zelfs negatieve.U kunt ook none vergelijken met string, string met int.
Vergelijking van verschillende gegevenstypen is niet toegestaan ​​in python 3.
Dit is bekend bij de meeste ontwikkelaars, maar we kregen te maken met een randgeval waarbij NotImplementedType werd vergeleken met int en dit zal niet werken in python 3.

Codefragment:

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

Als we dit opslaan met phone\_number\_validation.py en voer de code uit:

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

Compatibel Oplossing:
We moeten controleren of base.PHONE\_NO\_SIZE\_LIMIT is geïmplementeerd of niet, zo niet, dan moeten we het afhandelen. Zoals:

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 wiskundige functies:
Vergelijking kan niet werken in int naar none, int tot str, none naar str in python 3, dus de wiskundige functie min en Max zijn ook veranderd in 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"

Compatibele oplossing:
1. De lijst moet gegevens van één type bevatten: string of int
2. Met één type als none aanwezig is, kunnen we onze eigen methode hebben om dit als volgt af te handelen.

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 codering / decodering

Terwijl we een string coderen in Python 2 als we dit volgen .encode(‘hex’) Dit werkt niet in 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

Compatibele oplossing:
We moeten codecs gebruiken voor Python 3 en 2. In Python 3 zijn invoer en uitvoer beide 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"

Tekenreeks in hoofdletters:

string.uppercase werkt niet in 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"

Compatibele oplossing:
Gebruik ascii\_uppercase

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

hasattr ():

hasattr() vertoont een enorm randgeval bij het schrijven van Python 2 en 3 compatibele code. hasattr() controleert op het bestaan ​​van een attribuut door te proberen het op te halen.

Python 2

hasattr ( object , naam )
De argumenten zijn een object en een string. Het resultaat is True als de string de naam is van een van de kenmerken van het object, False als dat niet het geval is. (Dit wordt geïmplementeerd door getattr (object, naam) aan te roepen en te kijken of het een uitzondering oproept of niet.)

Python 3

hasattr ( object , naam ) De argumenten zijn een object en een string. Het resultaat is True als de string de naam is van een van de kenmerken van het object, False als dat niet het geval is. (Dit wordt geïmplementeerd door getattr (object, naam) aan te roepen en te kijken of het een AttributeError oproept of niet.)

Meer details vindt u hier: (https://medium.com/@k.wahome/python-2-vs-3-hasattr-behaviour-f1bed48b068)

Voorbeeldcode

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

Sla het bovenstaande fragment op met 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"

Compatibele oplossing:
Om codecompatibiliteit te maken in Python 2 en Python 3, moeten we de \_\_getattr\_\_ -functie wijzigen zoals hieronder.

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

Dict wordt geordend in python 3:

Vanaf Python 3.6+ is het woordenboek nu standaard invoeging besteld .

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

Compatibele code:
Idealiter zou dit de applicatiecode niet moeten breken, omdat deze veranderde in ongeordend naar geordend dict. Als we nog steeds hetzelfde resultaat nodig hebben in beide Python-versies (volgorde is belangrijk, testcase mislukt), moeten we OrderedDict gebruiken om beide taaluitvoer hetzelfde te houden.

Hashing:

In python 2 kan invoer worden getypt unicode en str, maar in Python 3 heeft het bytes

Compatibele oplossing:
Python 3 heeft als invoer, waarbij python 2 werkt met type unicode en str beide.

# 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\_\_ operator overbelast:

In python 3, de \_\_div\_\_ operator lijkt niet te bestaan ​​aangezien deze volledig vervangen is door \_\_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"

Compatibele oplossing:
In Python 3.x moeten we de \_\_truediv\_\_ operators overbelasten, niet de \_\_div\_\_ operator. Om code compatibel te maken, moeten we beide methoden behouden, zoals:

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

We doen de base64-codering met base64.b64encode(). In Python 2 kunnen we unicode of str als invoer doorgeven. Maar in python 3 heeft het de bytes nodig als invoer.

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

Compatibele oplossing:
We kunnen onze eigen methode hebben voor base64-codering en deze kan invoer hebben string en bytes beide.

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

Ingebouwde ronde methode:

Python2 : afronding is voltooid verwijderd van (dus. round (0.5) is bijvoorbeeld 1.0 en round (-0.5) is -1.0)
Python 3 : Afronding wordt gedaan naar de even keuze (dus bijvoorbeeld zowel ronde (0,5) als ronde (-0,5) zijn 0, en ronde (1.5) is 2).

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

Compatibele oplossing:
We hebben o je eigen ronde methode die hetzelfde werkt als Python 2 round in python 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. pack input type:

input type is str, Python 3 zou

# 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

Compatibele oplossing:
encode de invoer .

lijst begrijpend variabele scope changes:

In Python 3 betekent het gebruik van de omsluitende scope dat de variabele voor het begrijpen van de lijst geen toegang heeft tot de planvariabele buiten de functie, dit was niet het geval in 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

Compatibele oplossing:
We moeten dergelijke gevallen vermijden. Voor de tweede methode (two\_or\_three\_with\_method) moeten we x-waarde doorgeven als een argument.

math.floor en math.ceil retourneert gegevenstype gewijzigd:

In python 2 retourneren de floor en ceil het gegevenstype float, maar in python 3 retourneert het int gegevenstype.

# 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

Compatibele oplossing:
We kunnen maak de uitvoer als een float in python 3. Het heeft geen invloed op python 2, float(floor(4.345))

Een python 2 pickled object in python 3:

Zoals gezegd maken we code compatibel om beide python-versies uit te voeren. we ondervonden een probleem terwijl het object werd gebeitst in Python 2, maar we kunnen het niet ontkoppelen in python 3. Dit kan ook gebeuren voor Redis Pickled-objecten in de cache.

pickle.load(), standaard is om te proberen alle stringgegevens te decoderen als ASCII, en dat decoderen mislukt. Zie de

pickle.load() documentatie :

Optionele trefwoordargumenten zijn fix\_imports , codering en fouten , die worden gebruikt om de compatibiliteitsondersteuning voor pickle-stream gegenereerd door Python 2 te controleren. Als fix\_imports waar is, pickle zal proberen de oude Python 2-namen toe te wijzen aan de nieuwe namen die in Python 3 worden gebruikt. De codering en fouten vertellen augurk hoe 8-bits stringinstanties die door Python zijn gepickt, moeten worden gedecodeerd 2; deze zijn standaard respectievelijk ‘ASCII’ en ‘strikt’. De codering kan bytes zijn om deze 8-bit stringinstanties als bytesobjecten te lezen.

https : // stackoverflow.com / questions / 28218466 / unpickling-a-python-2-object-with-python-3

Compatibele oplossing:
We kunnen de onderstaande methode gebruiken om objecten los te koppelen.

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

Fixes voor bibliotheken van derden:

In ons project gebruiken we veel pakketten van derden, terwijl we ze bijwerken, kregen we te maken met enkele randgevallen . U kunt dit overslaan als u er geen gebruikt.

  1. Django :
    een. Django-migratiebestanden
    Toen we Django makemigrations in python 3 uitvoerden, zagen we nieuwe migratiebestanden. maar hetzelfde gebeurde niet voor python 2. Er kunnen meerdere redenen voor zijn.

b prefix: Bij makemigratie worden de meeste nieuwe bestanden gegenereerd zonder b prefix voor tekenreekswaarden. Dit komt omdat alle letterlijke tekenreeksen die in uw modellen en velden worden gebruikt (bijv. “ verbose\_name , “ related\_name , etc.), moeten consistent byte-strings of tekst (unicode) strings zijn in zowel Python 2 als 3.

Compatibele oplossing: De gemakkelijkste manier om één migratie voor nieuwe migratie tot stand te brengen, voeg from \_\_future\_\_ import unicode\_literal toe aan alle modellenbestanden. Voor bestaande migratiebestanden gebruiken we makemigration en dat zou maar één keer moeten gebeuren, of we kunnen b prefix verwijderen uit bestaande migratiebestanden.

Keuzeveld: In modellen gebruiken we dict.items (). Zoals we nu weten, worden de dict geordend in python 3 , zodat de waarden die worden geretourneerd van dict.items () zullen verschillen in Python 2 en Python 3.

Compatibele oplossing: Om compatibel te maken voor beide hebben we gesorteerd (dict. items ()) en gegenereerd migratiebestand dat nu compatibel is voor beide Python-versies.

b. “ Object” wordt weergegeven in de beheerdersconsole
Voor python 3 in de beheerdersconsole kunnen we Object zien als veldwaarde in plaats van een tekenreeks. if gebeurde omdat onze modelklasse een methode had.

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

We kunnen str-methode \_\_str\_\_ hebben en dat werkt voor zowel Python 2 als Python 3. Maar het zal mislukken als de str-versie niet-ASCII-tekens heeft.

Compatibel Oplossing: Heb de oplossing van hier , @python\_2\_unicode\_compatible decorateur toegevoegd voor modellen en gewijzigd \_\_unicode\_\_ naar \_\_str\_\_.

c . Django-queryobject slicing
Django-queryobject heeft de mogelijkheid om records op te halen. voor Django-versie (1.11), python 2 ondersteunt het slicing voor int en str. In Python 3 ondersteunt het alleen het doorsnijden van de 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]: ]>

Compatibele oplossing: Vermijd string-slicing, dat is sowieso geen goede benadering.

2. Redis : Redis is een algemeen gebruikt Python-pakket. redis-py 3.0 introduceert veel nieuwe functies, maar er moesten tijdens het proces verschillende achterwaarts incompatibele wijzigingen worden aangebracht.

Compatibele oplossing:
https://pypi.org/project/redis/ van hieruit kunnen we de wijzigingen bekijken en hoe u ze compatibel kunt maken code. We hebben onze eigen Redis-methoden gemaakt die compatibel zijn met beide Redis-versies. Zoals

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 is een handige app die ondersteunt automatische of handmatige queryset-caching en automatische granulaire, gebeurtenisgestuurde invalidatie. Er is een gotcha terwijl Django cacheops waarden opslaan in Redis, het maakt ze een pickle-object.

In python 2 zijn er 3 verschillende protocollen (0, 1, 2) en de standaard is 0. In python 3 zijn er 5 verschillende protocollen (0, 1, 2, 3, 4) en de standaard is 3.

Pickle gebruikt het standaard augurkprotocol om gegevens te dumpen. python 3 als we een pickle-object maken en willen unpickle in Python 2 zal niet werken omdat pickle-protocol 3 niet beschikbaar is in python 2.

Compatibele oplossing:
We kunnen de protocolparameter specificeren wanneer pickle.dump wordt aangeroepen.
django-cacheops hebben geen een optie om het augurkprotocol te verstrekken. We gebruikten monkey-patching om dit op te lossen.

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

Zoals hierboven vermeld, hoe een python 2 los te koppelen ingelegd object in python 3 . We willen gegevens ophalen in python 3, we kunnen UnicodeDecodeError onder ogen zien vanwege picking gedaan in verschillende Python-versies.
dit wordt ook gesorteerd met behulp van 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 : we hebben een methode voor het verwijderen van sleutels op basis van het patroon. In Python 2 gebruiken we de versie

1.6.5 het zoeken / verwijderen van de sleutel gebeurde zonder scan en maar voor Python 3 hebben we de versie bijgewerkt naar

2.1 waar het zoeken naar patronen gebeurt met behulp van Redis-scan, dat maakt het zo traag. Dit veroorzaakte het probleem. Git-hubprobleem hiervoor .

Compatibele oplossing:
We hebben het probleem opgelost met de oude manier van patroon verwijderen. in plaats van cache.delete\_pattern(pattern) aan te roepen, doen we

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

Whats Next

In (deel twee ) van deze blog, waar we zullen onderzoeken hoe we naar Python 3 kunnen gaan zonder downtime met voortdurende ontwikkeling.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *