Migration auf Python 3: Die HealthifyMe-Erfahrung.

Veröffentlicht

( 18. November 2020)

Hallo!, Dieser Blog Hier geht es darum, wie Sie die Python 3-Migration eines älteren Monolith-Projekts ohne Ausfallzeiten durchführen können. Wir haben 2 Teile dafür.

Erster Blog: Dieser Blog enthält Informationen darüber, warum Sie auf Python 3 migrieren sollten, und seltene Unterschiede zwischen Python 2 und Python 3 sowie Kompatible Lösungen für .

(Zweiter Blog): Wie HealthifyMe zu Python 3 wechselte, ohne Ausfallzeiten bei der laufenden Entwicklung zu haben.

Einführung

Python ist die primäre Codierungssprache bei HealthifyMe. Wir verwenden Python 3 für alle neuen Projekte, aber unser Legacy-Projekt lief noch mit Python 2.7 mit Django (1.11). Unser Monolith-Projekt ist 7 Jahre alt und enthält mehr als 2 Millionen Zeilen Python-Code. Einige der Gründe, warum wir zu Python 3 gewechselt sind:

  1. Die Unterstützung für Python 2 wurde eingestellt: Nach Januar 2020 wird Python 2 nicht mehr unterstützt.
  2. Python 3-Übernahme: As In den meisten Unternehmen übernimmt das Open-Source-Projekt bereits Python 3. Neue Bibliotheken, Tools, Module und Frameworks werden in Python 3 geschrieben.
    Das vorhandene Open-Source-Projekt wurde ebenfalls migriert und es wurden neue Funktionen, Korrekturen und Sicherheitsverbesserungen vorgenommen kommen in Python 3.
  3. Software-Sicherheit: Die Gewährleistung der Software-Sicherheit ist eine gesetzliche Anforderung, insbesondere wenn Sie haben es mit persönlichen Informationen im Bereich der DSGVO zu tun. Die Aktualisierung Ihrer Software gehört verständlicherweise zu den Best Practices für die Sicherheit. und ein veralteter Python-Interpreter sind so gut wie garantiert wird während einer Sicherheitsüberprüfung als rote Fahne angezeigt. Sicherheitstest-Tools wie Black Duck haben viele Schwachstellen, Exploits und Sicherheitsprobleme in Python 2 gemeldet. Die meisten davon sind in den neuesten Python 3-Versionen (3.7.5, 3.8.0) behoben.
  4. Leistung und neue Funktionen : Python 3 bietet eine bessere Leistung als Python 2. Das neue Softwareprodukt, das Python 3 verwendet, hat eine Steigerung der CPU-Leistung um 12\% und eine Verbesserung der Verwendung von Speicherressourcen um 30\% gemeldet. .
    Außerdem gibt uns Python 3:
    * native asynchrone Programmierung.
    * Typanmerkungen
    Sie können damit die statische Code-Analyse und die allgemeine Benutzerfreundlichkeit verbessern.
    * verkettete Ausnahmen , die besonders beim Debuggen nützlich sind.
    * andere hilfreiche Funktionen , die das Codieren in Python wesentlich effizienter machen.

Diese Liste wird fortgesetzt und wird mit Sicherheit wachsen mit jeder neuen Python 3-Version.

Dies sind einige Gründe für die Migration auf Python 3. Wir haben ungefähr 12 bis 15 Entwickler-Backend-Teams. Dies ist unser Basisprojekt mit einer täglichen Release-Version von 7 bis 10, die Fehlerbehebungen, Verbesserungen, Sicherheitskorrekturen, die Entwicklung neuer Funktionen usw. enthält. Unsere größte Herausforderung bestand darin, den aktuellen Entwicklungsprozess nicht zu stoppen. Wir mussten sicherstellen, dass unser Projekt mit Python 3.X kompatibel ist, ohne die Kompatibilität mit Python 2.X zu beeinträchtigen. Die Migration wurde von einem Entwickler geleitet (natürlich mit Hilfe anderer Entwickler).

In diesem Artikel werden wir versuchen, alle verschiedenen Schritte, Probleme und einige weitere Details zu veranschaulichen

Unterschied zwischen Python 2 und Python 3.

Wir können den gemeinsamen Unterschied hier herausfinden:

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

Nun werden wir einige der seltenen und Randfälle kurz beschreiben konfrontiert, als wir unsere Migration gestartet haben.

Datentypvergleich:

1. In Python 2 funktioniert das Vergleichen eines integer mit none, sodass none weniger berücksichtigt wird als eine ganze Zahl, auch negative.Sie können auch none mit string, string mit .
Ein anderer Datentypvergleich ist in Python 3 nicht zulässig.
Dies ist den meisten Entwicklern bekannt, aber wir hatten einen Randfall, in dem NotImplementedType mit int und dies funktioniert nicht in Python 3.

Code-Snippet:

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

Wenn wir dies speichern mit phone\_number\_validation.py und führen Sie den Code aus:

# 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ösung:
Wir müssen prüfen, ob base.PHONE\_NO\_SIZE\_LIMIT implementiert ist oder nicht, wenn nicht, müssen wir damit umgehen. Wie:

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-Funktionen:
Vergleich kann nicht in int to none, int bis str, none bis str in Python 3, daher haben sich auch die mathematischen Funktionen min und Max in Python 3 geändert.

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

Kompatible Lösung:
1. Die Liste sollte Daten eines Typs enthalten, entweder string oder int
2. Mit einem Typ, wenn none vorhanden ist, können wir unsere eigene Methode verwenden, um dies wie folgt zu behandeln.

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-Codierung / -Decodierung

Während wir eine Zeichenfolge in Python 2 codieren, wenn wir dieser .encode(‘hex’) Dies funktioniert nicht 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

Kompatible Lösung:
Wir sollten codecs für Python 3 und 2 verwenden. In Python 3 ist die Eingabe und Ausgabe beide byte Datentyp.

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

Großbuchstaben:

string.uppercase funktioniert in Python 3 nicht.

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

Kompatible Lösung:
Verwenden Sie ascii\_uppercase

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

hasattr ():

hasattr() bietet beim Schreiben von Python 2 und 3 einen großen Randfall kompatibler Code. hasattr() prüft, ob ein Attribut vorhanden ist, indem versucht wird, es abzurufen.

Python 2

hasattr ( Objekt , Name )
Die Argumente sind ein Objekt und eine Zeichenfolge. Das Ergebnis ist True, wenn die Zeichenfolge der Name eines der Objektattribute ist, False, wenn nicht. (Dies wird implementiert, indem Sie getattr (Objekt, Name) aufrufen und prüfen, ob eine Ausnahme ausgelöst wird oder nicht.)

Python 3

hasattr ( Objekt , name ) Die Argumente sind ein Objekt und eine Zeichenfolge. Das Ergebnis ist True, wenn die Zeichenfolge der Name eines der Objektattribute ist, False, wenn nicht. (Dies wird implementiert, indem getattr (Objekt, Name) aufgerufen und überprüft wird, ob ein AttributeError ausgelöst wird oder nicht.)

Weitere Details finden Sie hier: (https://medium.com/@k.wahome/python-2-vs-3-hasattr-behaviour-f1bed48b068)

Beispielcode

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

Speichern Sie das obige Snippet mit 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"

Kompatible Lösung:
Um die Codekompatibilität in Python 2 und Python 3 zu gewährleisten, müssen wir die Funktion \_\_getattr\_\_ wie folgt ändern.

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

Dict wird in Python 3 geordnet:

Ab Python 3.6+ ist das Wörterbuch jetzt Standard Einfügung angeordnet .

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

Kompatibler Code:
Idealerweise sollte dies den Anwendungscode nicht beschädigen, da er in ungeordnet zu geordnetem Diktat geändert wurde. Wenn immer noch dasselbe Ergebnis in beiden Python-Versionen benötigt wird (Reihenfolge ist wichtig, Testfall schlägt fehl), müssen wir OrderedDict verwenden, um die Ausgabe beider Sprachen gleich zu halten.

Hashing:

In Python 2 kann die Eingabe unicode und str, aber in Python 3 benötigt es 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

Kompatible Lösung:
Python 3 benötigt bytes als Eingabe, wobei Python 2 mit den Typen unicode und str arbeitet.

# 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\_\_ Überladung des Operators:

In Python 3 wird die Der Operator \_\_div\_\_ scheint nicht zu existieren, da er vollständig durch \_\_truediv\_\_ ersetzt wurde.

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"

Kompatible Lösung:
In Python 3.x müssen die Operatoren \_\_truediv\_\_ überladen werden, nicht die Operatoren \_\_div\_\_ Operator. Um Code kompatibel zu machen, müssen beide Methoden wie folgt beibehalten werden:

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

Wir führen die Base64-Codierung mit base64.b64encode() durch. In Python 2 können wir unicode oder str als Eingabe übergeben. In Python 3 wird jedoch die Eingabe bytes als Eingabe benötigt.

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

Kompatible Lösung:
Wir können unsere eigene Methode für die Base64-Codierung verwenden und string und 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

Eingebaute runde Methode:

Python2 : Rundung erfolgt von entfernt (so ist beispielsweise runde (0,5) 1,0 und runde (-0,5) -1,0)
Python 3 : Es wird auf die gerade Auswahl (so sind beispielsweise sowohl Runde (0,5) als auch Runde (-0,5) 0 und Runde (1,5) 2).

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

Kompatible Lösung:
Wir haben o erstellt Ihre eigene Round-Methode funktioniert genauso wie 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-Eingabetyp:

Der Eingabetyp ist str, Python 3 sollte

# 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

Kompatible Lösung:
encode die Eingabe .

Listenverständnisvariable scope Änderungen:

In Python 3 bedeutet die Verwendung der Listenverständnisvariablen mit eingeschlossenem Bereich, dass Sie außerhalb der Funktion nicht auf die Planvariable zugreifen können Dies ist in Python 2 nicht der Fall.

# 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

Kompatible Lösung:
Wir sollten solche Fälle vermeiden. Für die zweite Methode (two\_or\_three\_with\_method) müssen wir den x-Wert als Argument übergeben.

math.floor und der Rückgabedatentyp math.ceil wurde geändert:

In Python 2 geben der Floor- und Ceil-Datentyp float zurück, in Python 3 wird jedoch der Datentyp int zurückgegeben.

# 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

Kompatible Lösung:
Wir können Machen Sie die Ausgabe als Float in Python 3. Es hat keine Auswirkungen auf Python 2, float(floor(4.345))

Aufheben der Auswahl a In Python 3 eingelegtes Objekt in Python 3:

Wie bereits erwähnt, machen wir Code kompatibel, um beide Python-Versionen auszuführen. Während das Objekt in Python 2 eingelegt ist, ist ein Problem aufgetreten, aber wir können es in Python 3 nicht entfernen. Dies kann auch bei zwischengespeicherten Redis-Objekten auftreten.

pickle.load(), Standardmäßig wird versucht, alle Zeichenfolgendaten als ASCII zu dekodieren. Diese Dekodierung schlägt fehl. Weitere Informationen finden Sie in der Dokumentation

pickle.load() :

Optionale Schlüsselwortargumente sind fix\_imports , Codierung und Fehler , mit denen die Kompatibilitätsunterstützung für den von Python 2 generierten Pickle-Stream gesteuert wird. Wenn fix\_imports true ist, pickle versucht, die alten Python 2-Namen den neuen Namen zuzuordnen, die in Python 3 verwendet werden. Die Codierungs- und -Fehler weisen pickle an, wie von Python ausgewählte 8-Bit-String-Instanzen zu decodieren sind 2; Diese sind standardmäßig „ASCII“ bzw. „streng“. Die Codierung kann Bytes sein, um diese 8-Bit-Zeichenfolgeninstanzen als Bytes-Objekte zu lesen.

https ://Paketüberfluss.com / question / 28218466 / Unpickling-a-Python-2-Objekt-mit-Python-3

Kompatible Lösung:
Mit der folgenden Methode können wir Objekte entfernen.

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

Korrekturen an Bibliotheken von Drittanbietern:

In unserem Projekt verwenden wir viele Pakete von Drittanbietern, während wir sie aktualisieren . Sie können dies überspringen, wenn Sie keinen von ihnen verwenden.

  1. Django :
    a. Django-Migrationsdateien
    Als wir Django makemigrations in Python 3 ausführen, wurden neue Migrationsdateien angezeigt. Für Python 2 war dies jedoch nicht der Fall. Dies kann mehrere Gründe haben.

b Präfix: Bei der Makemigration werden die meisten neuen Dateien generiert, ohne dass b prefix für Zeichenfolgenwerte vorhanden ist. Dies liegt daran, dass alle in Ihren Modellen und Feldern verwendeten Zeichenfolgenliterale (z „ verbose\_name„, „ related\_name„ usw.) müssen sowohl in Python 2 als auch in Python konsistent entweder Byte-Strings oder Text-Strings (Unicode) sein.

Kompatible Lösung: Der einfachste Weg, eine Migration für eine neue Migration zu erreichen. Fügen Sie allen Modelldateien from \_\_future\_\_ import unicode\_literal hinzu. Für vorhandene Migrationsdateien führen wir entweder makemigration aus und dies sollte nur einmal geschehen, oder wir können b prefix aus vorhandenen Migrationsdateien entfernen. P. >

Auswahlfeld: In Modellen verwenden wir dict.items (). Da wir jetzt wissen, dass das -Dikt in Python 3 geordnet wird, unterscheiden sich die von dict.items () zurückgegebenen Werte in Python 2 und Python 3.

Kompatible Lösung: Um die Kompatibilität für beide zu gewährleisten, haben wir sortiert (dikt. items ()) und generierte Migrationsdatei, die jetzt für beide Python-Versionen kompatibel ist.

b. Anzeige von „ Objekt“ in der Administrationskonsole
Für Python 3 in der Administratorkonsole wird das Objekt als Feldwert anstelle einer Zeichenfolge angezeigt. wenn dies geschah, weil unsere Modellklasse eine Methode hat.

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

Wir können die str-Methode \_\_str\_\_ haben, die sowohl für Python 2 als auch für Python 3 funktioniert. Sie schlägt jedoch fehl, wenn die str-Version Nicht-ASCII-Zeichen enthält.

Kompatibel Lösung: Habe die Lösung von hier erhalten und @python\_2\_unicode\_compatible Dekorateur hinzugefügt für Modelle und geändert \_\_unicode\_\_ zu \_\_str\_\_.

c . Schneiden von Django-Abfrageobjekten
Das Django-Abfrageobjekt verfügt über eine Slicing-Funktion zum Abrufen von Datensätzen. Für die Django-Version (1.11) unterstützt Python 2 das Slicing für int und str. In Python 3 wird nur das Durchschneiden des Int unterstützt.

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

Kompatible Lösung: Vermeiden Sie das Schneiden von Zeichenfolgen, das ohnehin kein guter Ansatz ist.

2. Redis : Redis ist ein häufig verwendetes Python-Paket. redis-py 3.0 führt viele neue Funktionen ein, erfordert jedoch mehrere abwärtsinkompatible Änderungen.

Kompatible Lösung:
https://pypi.org/project/redis/ Hier können wir die Änderungen herausfinden und herausfinden, wie sie kompatibel gemacht werden können Code. Wir haben unsere eigenen Redis-Methoden entwickelt, die mit beiden Redis-Versionen kompatibel sind. Wie

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 ist eine clevere App, die unterstützt das automatische oder manuelle Abfragen von Abfragesätzen und die automatische granulare ereignisgesteuerte Ungültigmachung. Es gibt ein Gotcha, während Django-Cacheops Werte in Redis speichern, wodurch sie zu einem Pickle-Objekt werden.

In Python 2 gibt es 3 verschiedene Protokolle (0, 1, 2) und die Standardeinstellung ist 0. In Python 3 gibt es 5 verschiedene Protokolle (0, 1, 2, 3, 4) und der Standardwert ist 3.

Pickle Verwenden Sie das Standard-Pickle-Protokoll, um Daten zu sichern Python 3 funktioniert nicht, wenn wir ein Pickle-Objekt erstellen und in Python 2 die Auswahl aufheben möchten, da das Pickle-Protokoll 3 in Python 2 nicht verfügbar ist.

Kompatible Lösung:
Wir können den Protokollparameter beim Aufrufen von pickle.dump angeben.
Django-Cacheops haben keine eine Option zur Bereitstellung des Pickle-Protokolls. Wir haben Affen-Patches verwendet, um dies zu klären.

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

Auch wie oben erwähnt, wie man Python 2 abhebt Eingelegtes Objekt in Python 3 . Wir möchten Daten in Python 3 abrufen. Wir können mit UnicodeDecodeError konfrontiert werden, da die Auswahl in verschiedenen Python-Versionen erfolgt.
Dies wird auch mithilfe von Patches sortiert

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 : Wir haben eine Methode zum Löschen von Schlüsseln basierend auf dem Muster. In Python 2 verwenden wir die Version

1.6.5. Das Suchen / Löschen von Schlüsseln erfolgte ohne Scan. Für Python 3 haben wir die Version jedoch auf

2.1 aktualisiert Wenn die Mustersuche mit dem Redis-Scan erfolgt, ist sie so langsam. Dies verursachte das Problem. Git-Hub-Problem für dieses .

Kompatible Lösung:
Wir haben das Problem nach der alten Methode zum Löschen von Mustern sortiert. Anstatt cache.delete\_pattern(pattern) aufzurufen, machen wir

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

Wie geht es weiter?

In (Teil zwei) ) dieses Blogs, , in dem wir untersuchen, wie Sie zu Python 3 wechseln können, ohne Ausfallzeiten bei der laufenden Entwicklung zu haben.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.