Migration vers Python 3: lexpérience HealthifyMe.

( 18 nov.2020)

Bonjour !, ce blog explique comment effectuer la migration Python 3 dun projet monolithique hérité sans interruption. Nous avons 2 parties pour cela.

Premier blog: Ce blog contient des informations sur les raisons pour lesquelles vous devriez migrer vers Python 3 et les rares différences de cas entre Python 2 et Python 3 et Solutions compatibles pour ces .

(Deuxième blog): Comment HealthifyMe est passé à Python 3 sans interruption du développement en cours.

Introduction

Python est le principal langage de codage chez HealthifyMe. Nous utilisons Python 3 pour tous les nouveaux projets mais notre ancien projet fonctionnait toujours avec Python 2.7 avec Django (1.11). Notre projet monolithe a 7 ans avec plus de 2 millions de lignes de code python. Certaines des raisons pour lesquelles nous sommes passés à python 3:

  1. Support abandonné pour python 2: Après janvier 2020, la prise en charge de Python 2 ne sera plus fournie.
  2. Python 3 Adoption: Comme la plupart des entreprises, le projet open-source adopte déjà Python 3. De nouvelles bibliothèques, outils, modules, frameworks seront écrits en Python 3.
    Le projet open-source existant a également migré et nouvelle fonctionnalité, correctifs, amélioration de la sécurité sont à venir en Python 3.
  3. Sécurité du logiciel: Garantir la sécurité du logiciel est une obligation légale, en particulier lorsque vous traitez des informations personnelles dans le domaine du RGPD. Garder votre logiciel à jour se classe naturellement parmi les meilleures pratiques en matière de sécurité, et un interpréteur Python obsolète seraient pratiquement garantis apparaissent comme un drapeau rouge lors dun audit de sécurité Des outils de test de sécurité comme Black Duck ont ​​signalé de nombreuses vulnérabilités, exploits et problèmes de sécurité dans Python 2. Et la plupart dentre eux sont corrigés dans les dernières versions de Python 3 (3.7.5, 3.8.0).
  4. Performances et nouvelles fonctionnalités : Python 3 a de meilleures performances que python 2. Le nouveau produit logiciel qui utilise python 3 a signalé une augmentation des performances du processeur de 12\% et une amélioration de 30\% de lutilisation des ressources mémoire .
    aussi, python 3 nous donne:
    * programmation asynchrone native.
    * annotations de type
    que vous pouvez utiliser pour améliorer lanalyse de code statique et la convivialité globale.
    * exceptions chaînées , qui sont particulièrement utiles lors du débogage.
    * dautres fonctionnalités utiles qui rendent le codage en Python beaucoup plus efficace.

Cette liste est longue, et elle ne manquera pas de sallonger avec chaque nouvelle version de Python 3.

Voici quelques raisons pour nous de migrer vers python 3. Nous avons environ 12 à 15 équipes de développeurs backend. Ceci est notre projet de base avec 7–10 versions de build par jour, avec des corrections de bogues, des améliorations, des correctifs de sécurité, le développement de nouvelles fonctionnalités, etc. Notre principal défi nétait pas darrêter le processus de développement actuel. Nous devions nous assurer que notre projet est compatible avec Python 3.X sans casser la compatibilité avec Python 2.X. La migration a été menée par 1 développeur (bien sûr, avec laide dautres développeurs).

Dans cet article, nous essaierons dillustrer toutes les différentes étapes suivies, les problèmes rencontrés et quelques détails supplémentaires .

Différence entre python 2 et python 3.

Nous pouvons trouver la différence commune ici:

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

Maintenant, nous allons décrire brièvement quelques-uns des cas rares et extrêmes que nous rencontrés lorsque nous avons commencé notre migration.

Comparaison des types de données:

1. Dans Python 2, comparer un integer à none fonctionnera, de sorte que none est considéré comme moins quun entier, même des nombres négatifs.De plus, vous pouvez comparer none avec string, string avec int.
La comparaison de types de données différents nest pas autorisée en python 3.
Ceci est connu de la plupart des développeurs, mais nous avons été confrontés à un cas particulier où NotImplementedType a été comparé à int et cela ne fonctionnera pas en python 3.

Extrait de code:

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

Si nous sauvegardons ceci avec phone\_number\_validation.py et exécutez le code:

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

Compatible Solution:
Nous devons vérifier si base.PHONE\_NO\_SIZE\_LIMIT est implémenté ou non, sinon nous devons le gérer. Comme:

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. Fonctions mathématiques min, max:
Comparaison du travail de cant dans int à none, int à str, none à str en python 3, donc les fonctions mathématiques min et Max ont également changé en 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"

Solution compatible:
1. La liste doit contenir des données dun type soit string ou int
2. Avec un type si none est là, nous pouvons avoir notre propre méthode pour gérer cela comme.

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

Codage / décodage hexadécimal

Alors que nous encodons une chaîne en python 2 si nous suivons ceci .encode(‘hex’) Cela ne fonctionnera pas en 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

Solution compatible:
Nous devrions utiliser codecs pour Python 3 et 2. En python 3, lentrée et la sortie sont toutes deux 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"

Chaîne en majuscules:

string.uppercase ne fonctionne pas en 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"

Solution compatible:
Utilisez ascii\_uppercase

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

hasattr ():

hasattr() présente un énorme cas de bord lors de lécriture de Python 2 et 3 code compatible. hasattr() vérifie lexistence dun attribut en essayant de le récupérer.

Python 2

hasattr ( object , nom )
Les arguments sont un objet et une chaîne. Le résultat est Vrai si la chaîne est le nom de l’un des attributs de l’objet, Faux dans le cas contraire. (Ceci est implémenté en appelant getattr (objet, nom) et en voyant sil déclenche une exception ou non.)

Python 3

hasattr ( objet , nom ) Les arguments sont un objet et une chaîne. Le résultat est True si la chaîne est le nom de l’un des attributs de l’objet, False sinon. (Ceci est implémenté en appelant getattr (objet, nom) et en vérifiant sil déclenche une AttributeError ou non.)

Veuillez trouver plus de détails ici: (https://medium.com/@k.wahome/python-2-vs-3-hasattr-behaviour-f1bed48b068)

Exemple de code

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

Enregistrez lextrait de code ci-dessus avec 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"

Solution compatible:
Pour rendre le code compatible dans Python 2 et Python 3, nous devons changer la fonction \_\_getattr\_\_ comme ci-dessous.

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

Dict devient ordonné en python 3:

À partir de Python 3.6+, le dictionnaire est désormais par défaut insertion ordonnée .

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

Code compatible:
Idéalement, cela ne devrait pas casser le code de lapplication, car il est passé de unordered à ordonné dict. Si cest toujours le cas, nous avons besoin du même résultat dans les deux versions de python (lordre est important, le cas de test échoue), nous devons utiliser OrderedDict pour garder la même sortie dans les deux langues.

Hachage:

En python 2, lentrée peut être saisie unicode et str, mais en Python 3, il a besoin de 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

Solution compatible:
Python 3 a besoin de bytes en entrée, où python 2 fonctionne avec le type unicode et str les deux.

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

surcharge de lopérateur \_\_div\_\_:

Dans Python 3, le Lopérateur \_\_div\_\_ semble ne pas exister car il a été entièrement remplacé par \_\_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"

Solution compatible:
Dans Python 3.x, nous devons surcharger les opérateurs \_\_truediv\_\_, pas les \_\_div\_\_. Pour rendre le code compatible, nous devons garder les deux méthodes comme:

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

Encodage Base64:

Nous faisons lencodage base64 en utilisant base64.b64encode(). Dans Python 2, nous pouvons passer unicode ou str comme entrée. Mais en python 3, il a besoin du bytes comme entrée.

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

Solution compatible:
Nous pouvons avoir notre propre méthode dencodage base64 et elle peut avoir des entrées string et bytes les deux.

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

Méthode darrondi intégrée:

Python2 : larrondi est effectué loin de (ainsi. par exemple, round (0.5) est 1.0 et round (-0.5) est -1.0)
Python 3 : larrondissement est effectué vers le choix pair (ainsi, par exemple, round (0.5) et round (-0.5) valent 0, et round (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

Solution compatible:
Nous avons créé o votre propre méthode round qui fonctionne de la même manière que Python 2 round en 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. type dentrée du pack:

le type dentrée est str, Python 3 devrait être 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

Solution compatible:
encode lentrée .

variable de compréhension de liste scope changements:

Dans Python 3, la variable de compréhension de liste utilise une portée englobante signifie que vous ne pourrez pas accéder à la variable de plan à lextérieur de la fonction, cétait pas le cas en 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

Solution compatible:
Nous devrions éviter de tels cas. Pour la deuxième méthode (two\_or\_three\_with\_method), nous devons passer la valeur x comme argument.

math.floor et math.ceil renvoient le type de données changé:

En python 2, le floor et ceil renvoient le type de données float mais en python 3, il renvoie le type de données 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

Solution compatible:
Nous pouvons rendre la sortie sous forme de flottant en python 3. Cela naura pas dimpact sur python 2, float(floor(4.345))

Décollez un python 2 pickled object into python 3:

Comme mentionné, nous rendons le code compatible pour exécuter les deux versions de python. nous avons rencontré un problème lorsque lobjet est picklé dans Python 2 mais nous ne sommes pas en mesure de décoller en python 3. Cela peut également arriver pour les objets mis en cache Redis Pickled.

pickle.load(), par défaut est dessayer de décoder toutes les données de chaîne en ASCII, et ce décodage échoue. Consultez la

pickle.load() documentation :

Les arguments de mot-clé facultatifs sont fix\_imports , encoding et erreurs , qui sont utilisées pour contrôler la compatibilité avec le flux pickle généré par Python 2. Si fix\_imports est vrai, pickle essaiera de mapper les anciens noms Python 2 aux nouveaux noms utilisés dans Python 3. Les erreurs dencodage et indiquent à pickle comment décoder les instances de chaînes 8 bits picklées par Python 2; ces valeurs par défaut sont respectivement «ASCII» et «strict». Le encodage peut être octets pour lire ces instances de chaîne de 8 bits comme des objets octets.

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

Solution compatible:
Nous pouvons utiliser la méthode ci-dessous pour décoller les objets.

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

Corrections de bibliothèques tierces:

Dans notre projet, nous utilisons de nombreux packages tiers, tandis que nous les mettons à jour, nous avons été confrontés à des cas extrêmes . Vous pouvez lignorer si vous nen utilisez aucun.

  1. Django :
    a. Fichiers de migration Django
    Lorsque nous exécutons Django makemigrations en python 3, nous voyons de nouveaux fichiers de migration. mais la même chose ne se produisait pas pour python 2. Il peut y avoir plusieurs raisons à cela.

b prefix: Sur makemigration, la plupart des nouveaux fichiers sont générés sans avoir b prefix pour les valeurs de chaîne. Ceci est dû au fait que tous les littéraux de chaîne utilisés dans vos modèles et champs (par exemple «  verbose\_name« , «  related\_name« , etc.), doit être systématiquement soit des chaînes doctets, soit des chaînes de texte (unicode) dans Python 2 et 3.

Solution compatible: Le moyen le plus simple de réaliser une migration pour une nouvelle migration, ajoutez from \_\_future\_\_ import unicode\_literal à tous les fichiers de modèles. Pour les fichiers de migration existants, nous exécutons makemigration et cela ne devrait se produire quune seule fois, ou nous pouvons supprimer b prefix des fichiers de migration existants.

Champ de choix: Dans les modèles, nous utilisons dict.items (). Comme nous savons maintenant que le dict devient ordonné en python 3 donc les valeurs renvoyées par dict.items () seront différentes en Python 2 et Python 3.

Solution compatible: Pour rendre compatible les deux, nous avons trié (dict. items ()) et le fichier de migration généré qui est maintenant compatible pour les deux versions de python.

b. Affichage de « Object» dans la console dadministration
Pour python 3 dans la console dadministration, nous pouvons voir Object comme valeur de champs au lieu dune chaîne. si cela se passait parce que notre classe de modèle avait une méthode.

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

Nous pouvons avoir la méthode str \_\_str\_\_ et qui fonctionne à la fois pour Python 2 et Python 3. Mais elle échouera si la version str contient des caractères non ASCII.

Compatible Solution: Obtention de la solution de ici , ajout du décorateur @python\_2\_unicode\_compatible pour les modèles et \_\_unicode\_\_ modifié en \_\_str\_\_.

c . Tranchage dobjet de requête Django
Lobjet de requête Django a une capacité de découpage pour récupérer des enregistrements. pour la version Django (1.11), python 2 prend en charge le découpage pour int et str. Dans Python 3, il ne prend en charge que le découpage à travers lint.

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

Solution compatible: Évitez le découpage des chaînes qui nest de toute façon pas une bonne approche.

2. Redis : Redis est un package Python couramment utilisé. redis-py 3.0 introduit de nombreuses nouvelles fonctionnalités, mais nécessitait plusieurs modifications incompatibles vers larrière dans le processus.

Solution compatible:
https://pypi.org/project/redis/ à partir dici, nous pouvons découvrir les modifications et comment rendre compatible code. Nous avons créé nos propres méthodes Redis compatibles avec les deux versions de Redis. Comme

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 est une application astucieuse qui prend en charge la mise en cache automatique ou manuelle des ensembles de requêtes et linvalidation granulaire automatique basée sur les événements. Il y a un piège pendant que Django cache des valeurs dans Redis, cela en fait un objet pickle.

Dans python 2, il existe 3 protocoles différents (0, 1, 2) et la valeur par défaut est 0.En python 3, il existe 5 protocoles différents (0, 1, 2, 3, 4) et la valeur par défaut est 3.

Pickle utilise le protocole pickle par défaut pour vider les données. python 3 si nous créons un objet pickle et que nous voulons décoller en Python 2 ne fonctionnera pas car le protocole pickle 3 nest pas disponible en python 2.

Solution compatible:
Nous pouvons spécifier le paramètre de protocole lors de lappel de pickle.dump.
les django-cacheops nont pas une option pour fournir le protocole pickle. Nous avons utilisé monkey patching pour résoudre ce problème.

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

Également comme mentionné ci-dessus comment Décollez un python 2 objet mariné en python 3 . Nous voulons obtenir des données en python 3, nous pouvons faire face à UnicodeDecodeError en raison de la sélection effectuée dans différentes versions de python.
cela est également trié à laide de correctifs

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 : nous avons une méthode pour supprimer des clés en fonction du modèle. Dans Python 2, nous utilisons la version

1.6.5 la recherche / suppression de clé se faisait sans scan et mais pour python 3, nous avons mis à jour la version en

2.1 où la recherche de modèle se produit à laide de lanalyse Redis, ce qui la rend si lente. Cela causait le problème. Problème de hub Git pour ce .

Solution compatible:
Nous avons trié le problème en utilisant lancienne méthode de suppression de motif. au lieu dappeler cache.delete\_pattern(pattern) nous faisons

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

Et maintenant

Dans (deuxième partie ) de ce blog, où nous allons explorer comment passer à python 3 sans avoir de temps darrêt avec le développement en cours.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *