Migración a Python 3: La experiencia HealthifyMe.

Publicado el

( 18 de noviembre de 2020)

¡Hola, este blog se trata de cómo realizar la migración de Python 3 de un proyecto monolito heredado sin tomar ningún tiempo de inactividad. Tenemos 2 partes para esto.

Primer blog: este blog tiene información sobre por qué debería migrar a Python 3 y la diferencia de casos raros en Python 2 y Python 3 y Soluciones compatibles para aquellos .

(Segundo blog): Cómo HealthifyMe pasó a Python 3 sin tener tiempo de inactividad con el desarrollo continuo.

Introducción

Python es el lenguaje de codificación principal en HealthifyMe. Estamos usando Python 3 para todos los proyectos nuevos, pero nuestro proyecto heredado todavía se estaba ejecutando con Python 2.7 con Django (1.11). Nuestro proyecto monolito tiene 7 años y más de 2 millones de líneas de código Python. Algunas de las razones por las que nos mudamos a Python 3:

  1. Se eliminó la compatibilidad con Python 2: Después de enero de 2020, no se proporcionará soporte para Python 2.
  2. Adopción de Python 3: Como En la mayoría de las empresas, el proyecto de código abierto ya está adoptando Python 3. Las nuevas bibliotecas, herramientas, módulos y marcos se escribirán en Python 3.
    El proyecto de código abierto existente también se migró y nuevas funciones, correcciones y mejoras de seguridad. vienen en Python 3.
  3. Seguridad del software: Garantizar la seguridad del software es un requisito legal, especialmente cuando está tratando con información personal en el ámbito de GDPR. Mantener su software actualizado, comprensiblemente, ocupa un lugar muy alto entre las mejores prácticas de seguridad, y un intérprete de Python desactualizado estaría casi garantizado aparecer como una bandera roja durante una auditoría de seguridad. Las herramientas de prueba de seguridad como Black Duck han reportado muchas vulnerabilidades, exploits y problemas de seguridad en Python 2. Y la mayoría de ellos están corregidos en las últimas versiones de Python 3 (3.7.5, 3.8.0).
  4. Rendimiento y nuevas funciones : Python 3 tiene un mejor rendimiento que Python 2. El nuevo producto de software que usa python 3 ha reportado un aumento del 12\% en el rendimiento de la CPU y una mejora del 30\% en el uso de recursos de memoria .
    también, python 3 nos da:
    * programación asíncrona nativa.
    * escriba anotaciones
    que puede utilizar para mejorar el análisis de código estático y la usabilidad general.
    * excepciones encadenadas , que son especialmente útiles al depurar.
    * otras funciones útiles que hacen que la codificación en Python sea mucho más eficiente.

Esta lista continúa y seguramente crecerá con cada nueva versión de Python 3.

Estas son algunas de las razones para migrar a Python 3. Tenemos aproximadamente entre 12 y 15 equipos de backend de desarrolladores. Este es nuestro proyecto base con 7 a 10 versiones diarias, con correcciones de errores, mejoras, correcciones de seguridad, desarrollo de nuevas funciones, etc. Nuestro principal desafío no era detener el proceso de desarrollo actual. Tuvimos que asegurarnos de que nuestro proyecto sea compatible con Python 3.X sin romper la compatibilidad con Python 2.X. La migración fue dirigida por 1 desarrollador (por supuesto, con la ayuda de otros desarrolladores).

En este artículo, intentaremos ilustrar todos los pasos tomados, los problemas enfrentados y algunos detalles más. .

Diferencia entre python 2 y python 3.

Podemos encontrar la diferencia común aquí:

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

Ahora describiremos brevemente algunos de los casos raros y extremos que cuando comenzamos nuestra migración.

Comparación de tipos de datos:

1. En Python 2, la comparación de integer con none funcionará, de modo que none se considera menos que un número entero, incluso los negativos.Además, puede comparar none con string, string con int.
La comparación de tipos de datos diferentes no está permitida en Python 3.
Esto es conocido por la mayoría de los desarrolladores, pero nos enfrentamos al caso límite en el que NotImplementedType se comparó con int y esto no funcionará en Python 3.

Fragmento de código:

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 guardamos esto con phone\_number\_validation.py y ejecute el código:

# 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 Solución:
Tenemos que comprobar si base.PHONE\_NO\_SIZE\_LIMIT está implementado o no, si no, entonces tenemos que manejarlo. Me gusta:

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. Funciones matemáticas mínimas y máximas:
La comparación no funciona en int para none, int a str, none a str en python 3, por lo que la función matemática min y Max también han cambiado 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"

Solución compatible:
1. La lista debe tener datos de un tipo, ya sea string o int
2. Con un tipo, si none está ahí, podemos tener nuestro propio método para manejar esto.

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

Codificación / decodificación hexadecimal

Mientras codificamos una cadena en Python 2 si seguimos esto .encode(‘hex’) Esto no funcionará 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

Solución compatible:
Deberíamos usar codecs para Python 3 y 2. En Python 3, la entrada y la salida son byte tipo de datos.

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

Cadena en mayúsculas:

string.uppercase no funciona 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"

Solución compatible:
Utilice ascii\_uppercase

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

hasattr ():

hasattr() presenta una gran ventaja al escribir Python 2 y 3 código compatible. hasattr() comprueba la existencia de un atributo al intentar recuperarlo.

Python 2

hasattr ( objeto , nombre )
Los argumentos son un objeto y una cadena. El resultado es Verdadero si la cadena es el nombre de uno de los atributos del objeto, Falso si no. (Esto se implementa llamando a getattr (objeto, nombre) y viendo si genera una excepción o no).

Python 3

hasattr ( objeto , nombre ) Los argumentos son un objeto y una cadena. El resultado es Verdadero si la cadena es el nombre de uno de los atributos del objeto, Falso si no. (Esto se implementa llamando a getattr (objeto, nombre) y viendo si genera un AttributeError o no).

Encuentre más detalles aquí: (https://medium.com/@k.wahome/python-2-vs-3-hasattr-behaviour-f1bed48b068)

Código de muestra

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

Guarde el fragmento anterior con 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"

Solución compatible:
Para hacer compatibilidad de código en Python 2 y Python 3, tenemos que cambiar la función \_\_getattr\_\_ como se muestra a continuación.

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

Dict se ordena en Python 3:

Desde Python 3.6+ en adelante, el diccionario ahora es predeterminado inserción ordenada .

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

Código compatible:
Idealmente, esto no debería romper el código de la aplicación, ya que cambió de desordenado a ordenado. Si aún así, necesitamos el mismo resultado en ambas versiones de Python (el orden es importante, el caso de prueba falla), tenemos que usar OrderedDict para mantener la salida de ambos idiomas igual.

Hash:

En Python 2, la entrada se puede escribir unicode y str, pero en Python 3 necesita 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

Solución compatible:
Python 3 necesita bytes como entrada, donde python 2 funciona con el tipo unicode y str ambos.

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

Sobrecarga del operador \_\_div\_\_:

En Python 3, el El operador \_\_div\_\_ parece no existir ya que fue reemplazado por \_\_truediv\_\_ por completo.

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"

Solución compatible:
En Python 3.x, tenemos que sobrecargar los operadores \_\_truediv\_\_, no los \_\_div\_\_ operador. Para hacer el código compatible, debemos mantener ambos métodos como:

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

Codificación Base64:

Hacemos la codificación base64 usando base64.b64encode(). En Python 2 podemos pasar unicode o str como entrada. Pero en Python 3 necesita bytes como entrada.

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

Solución compatible:
Podemos tener nuestro propio método para la codificación base64 y puede tener entrada string y bytes ambos.

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étodo de ronda incorporado:

Python2 : el redondeo está hecho lejos de (por ejemplo, round (0.5) es 1.0 y round (-0.5) es -1.0)
Python 3 : el redondeo se realiza hacia la opción par (entonces, por ejemplo, tanto round (0.5) como round (-0.5) son 0, y round (1.5) es 2).

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

Solución compatible:
Creamos o Nuestro propio método de ronda que funciona igual que Python 2 round en Python 3 también.

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. tipo de entrada del paquete:

el tipo de entrada es str, Python 3 debe ser 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

Solución compatible:
encode la entrada .

lista de variables de comprensión scope cambia:

En Python 3, el uso de la variable de comprensión de la lista incluye el alcance significa que no podrá acceder a la variable del plan fuera de la función, esto fue no es el caso 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

Solución compatible:
Deberíamos evitar estos casos. Para el segundo método (two\_or\_three\_with\_method) tenemos que pasar el valor x como argumento.

math.floor y el tipo de datos de retorno de math.ceil ha cambiado:

En python 2, el suelo y el techo devuelven el tipo de datos flotante, pero en python 3 devuelve el tipo de datos 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

Solución compatible:
Podemos hacer que la salida sea flotante en Python 3. No afectará a Python 2, float(floor(4.345))

Despedando un Python 2 escabechó el objeto en Python 3:

Como mencionamos, estamos haciendo el código compatible para ejecutar ambas versiones de Python. nos enfrentamos a un problema mientras el objeto está encurtido en Python 2, pero no podemos deshacerlo en Python 3. Esto también puede suceder para los objetos almacenados en caché de Redis Pickled.

pickle.load(), predeterminado es intentar decodificar todos los datos de cadena como ASCII, y esa decodificación falla. Consulte la

pickle.load() documentación :

Los argumentos de palabras clave opcionales son fix\_imports , codificación y errores , que se utilizan para controlar el soporte de compatibilidad para el flujo de pickle generado por Python 2. Si fix\_imports es verdadero, pickle intentará asignar los nombres antiguos de Python 2 a los nuevos nombres usados ​​en Python 3. Los codificación y errores le dicen a pickle cómo decodificar instancias de cadenas de 8 bits seleccionadas por Python 2; estos valores predeterminados son «ASCII» y «estricto», respectivamente. La codificación puede ser bytes para leer estas instancias de cadenas de 8 bits como objetos de bytes.

https ://desbordamiento de pila.com / questions / 28218466 / unpickling-a-python-2-object-with-python-3

Solución compatible:
Podemos usar el método siguiente para eliminar objetos.

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

Correcciones de bibliotecas de terceros:

En nuestro proyecto, estamos usando muchos paquetes de terceros, mientras que los actualizamos nos enfrentamos a algunos casos extremos . Puede omitir esto si no está usando ninguno de ellos.

  1. Django :
    a. Archivos de migración de Django
    Cuando ejecutamos Django makemigrations en python 3, estábamos viendo nuevos archivos de migración. pero no sucedió lo mismo con Python 2. Puede haber varias razones para esto.

prefijo b: Al realizar la migración, la mayoría de los archivos nuevos se generan sin tener b prefix para los valores de cadena. « verbose\_name , « related\_name«, etc.), debe ser consistentemente cadenas de bytes o cadenas de texto (unicode) tanto en Python 2 como en 3.

Solución compatible: La forma más fácil de lograr una migración para una nueva migración agregue from \_\_future\_\_ import unicode\_literal a todos los archivos de modelos. Para los archivos de migración existentes, ejecutamos makemigration y eso solo debería suceder una vez, o podemos eliminar b prefix de los archivos de migración existentes.

Campo de elección: En los modelos usamos dict.items (). Como sabemos ahora, el dict se ordena en Python 3 por lo que los valores devueltos por dict.items () serán diferentes en Python 2 y Python 3.

Solución compatible: Para que ambos sean compatibles, ordenamos (dict. items ()) y archivo de migración generado que ahora es compatible para ambas versiones de Python.

b. Visualización de « Object» en la consola de administración
Para python 3 en la consola de administración, podemos ver Object como valor de campo en lugar de una cadena. si estaba sucediendo porque nuestra clase modelo tiene un método.

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

Podemos tener el método str \_\_str\_\_ y eso funciona tanto para Python 2 como para Python 3. Pero fallará si la versión str tiene caracteres no ASCII.

Compatible Solución: Obtuve la solución de aquí , agregué @python\_2\_unicode\_compatible decorador para modelos y \_\_unicode\_\_ modificado a \_\_str\_\_.

c . Rebanado de objetos de consulta de Django
El objeto de consulta de Django tiene la capacidad de segmentar registros para recuperar registros. para la versión de Django (1.11), python 2 admite el corte para int y str. En Python 3, solo admite cortar el 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]: ]>

Solución compatible: Evite el corte de cadenas que de todos modos no es un buen enfoque.

2. Redis : Redis es un paquete de Python de uso común. redis-py 3.0 introduce muchas características nuevas, pero requiere que se realicen varios cambios incompatibles con versiones anteriores en el proceso.

Solución compatible:
https://pypi.org/project/redis/ desde aquí podemos conocer los cambios y cómo hacerlos compatibles código. Creamos nuestros propios métodos de Redis que son compatibles con ambas versiones de Redis. Me gusta

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 es una aplicación elegante que admite el almacenamiento en caché de conjuntos de consultas automático o manual y la invalidación granular automática basada en eventos. Hay un problema, mientras que Django cacheops almacena valores en Redis, los convierte en un objeto pickle.

En Python 2 hay 3 protocolos diferentes (0, 1, 2) y el predeterminado es En Python 3 hay 5 protocolos diferentes (0, 1, 2, 3, 4) y el predeterminado es 3.

Pickle usa el protocolo pickle predeterminado para volcar datos. python 3 si hacemos un objeto pickle y queremos deshacerlo en Python 2 no funcionará porque el protocolo pickle 3 no está disponible en python 2.

Solución compatible:
Podemos especificar el parámetro de protocolo al invocar pickle.dump.
django-cacheops no tienen una opción para proporcionar el protocolo pickle. Usamos parches de mono para solucionar esto.

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

También, como se mencionó anteriormente, cómo Deseleccionar un python 2 objeto en escabeche en python 3 . Queremos obtener datos en Python 3, podemos enfrentar UnicodeDecodeError debido a la selección realizada en diferentes versiones de Python.
esto también se ordena usando parches

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 : tenemos un método para borrar claves basadas en el patrón. En Python 2 usamos la versión

1.6.5, la búsqueda / eliminación de claves se estaba realizando sin escanear y, para Python 3, actualizamos la versión a

2.1 donde la búsqueda de patrones ocurre usando el escaneo de Redis, eso lo está haciendo muy lento. Esto estaba causando el problema. Problema de Git hub para este .

Solución compatible:
Solucionamos el problema usando la forma antigua de borrar patrones. en lugar de llamar a cache.delete\_pattern(pattern) lo que estamos haciendo

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

¿Qué sigue

En (segunda parte ) de este blog, donde exploraremos cómo pasar a Python 3 sin tener tiempo de inactividad con el desarrollo continuo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *