Passage à Python 3¶
Django 1.5 est la première version de Django à prendre en charge Python 3. Le même code fonctionne aussi bien avec Python 2 (≥ 2.6.5) qu’avec Python 3 (≥ 3.2), grâce à la couche de compatibilité offerte par six.
Ce document est principalement destiné aux rédacteurs d’applications réutilisables qui souhaitent prendre en charge à la fois Python 2 et 3. Il présente également des lignes de conduites adoptées dans le code de Django.
Philosophie¶
Ce document suppose que les changements entre Python 2 et Python 3 vous sont familiers. Dans le cas contraire, lisez d’abord Python’s official porting guide. Le rafraîchissement de vos connaissances de la gestion de l’unicode dans Python 2 et 3 peut être utile ; la présentation Pragmatic Unicode est une bonne ressource.
Django a choisi la stratégie source compatible Python 2 et 3. Vous êtes évidemment libre de choisir une autre stratégie pour votre propre code, particulièrement si vous n’avez pas besoin de rester compatible avec Python 2. Mais les auteurs d’applications enfichables sont encouragés à adopter la même stratégie de transition que Django.
L’écriture de code compatible est bien plus facile si vous ciblez Python ≥ 2.6. Django 1.5 introduit des outils de compatibilité tels que django.utils.six, qui est une version adaptée du module six. Par commodité, des alias de compatibilité ascendante ont été introduits dans Django 1.4.2. Si votre application tire profit de ces outils, elle nécessitera au moins Django ≥ 1.4.2.
De toute évidence, l’écriture de code source compatible ajoute une certaine charge qui peut parfois être frustrante. Les développeurs de Django ont constaté que d’écrire du code Python 3 en veillant à ce qu’il reste compatible avec Python 2 est plus gratifiant que l’inverse. Non seulement le code sera plus pérenne, mais les avantages de Python 3 (comme une gestion plus saine du texte) se révèlent assez rapidement. La contrainte de Python 2 devient une exigence de rétrocompatibilité et nous, développeurs, sommes habitués à faire face à de telles contraintes.
Les outils de migration fournis par Django sont inspirés de cette approche et cela se reflète tout au long de ce guide.
Aides à la migration¶
Chaînes littérales unicode¶
Cette étape consiste à :
Ajouter from __future__ import unicode_literals au début de chaque module Python. Il est conseillé de le mettre vraiment dans tous les modules, sinon vous ne saurez jamais dans quel mode vous êtes sans chaque fois regarder au début du fichier concerné.
Enlever les préfixes u devant toutes les chaînes unicode.
Ajouter un préfixe b devant les chaînes d’octets.
L’application systématique de ces modifications garantit la rétrocompatibilité.
Cependant, les applications Django ne nécessitent généralement pas de chaînes d’octets, car Django n’expose que des interfaces unicode au programmeur. Python 3 décourage l’utilisation de chaînes d’octets, sauf pour les données binaires ou les interfaces orientées octets. Dans Python 2, les chaînes d’octets et les chaînes unicode sont pratiquement interchangeables tant qu’elles ne contiennent pas de données non ASCII. Profitez de ceci pour employer des chaînes unicode autant que possible en évitant les préfixes b.
Note
Le préfixe u de Python 2 est une erreur de syntaxe dans Python 3.2, mais il est de nouveau autorisé dans Python 3 suite à la PEP 414. Ainsi, cette transformation est facultative si vous ciblez Python ≥ 3.3. Mais c’est toujours recommandé, selon la philosophie « écrivons du code Python 3 ».
Gestion des chaînes¶
Le type unicode de Python 2 a été renommé str dans Python 3, str() a été renommé bytes et basestring a disparu. six offre des outils pour s’occuper de ces modifications.
Django contient aussi plusieurs classes et fonctions liées aux chaînes dans les modules django.utils.encoding et django.utils.safestring. Leurs noms contenait le mot str, qui n’a pas la même signification dans Python 2 et dans Python 3, et unicode, qui n’existe plus dans Python 3. Afin d’éviter des ambiguïtés et de la confusion, ces concepts ont été renommés bytes et text.
Voici les changements de noms effectués dans django.utils.encoding:
Ancien nom |
Nouveau nom |
---|---|
smart_str | smart_bytes |
smart_unicode | smart_text |
force_unicode | force_text |
Pour des motifs de rétrocompatibilité, les anciens noms fonctionnent encore avec Python 2. Avec Python 3, smart_str est un alias de smart_text.
Pour des motifs de compatibilité ascendante, les nouveaux noms sont valables dès Django 1.4.2.
Note
django.utils.encoding a été réécrit en profondeur dans Django 1.5 pour fournir une API plus cohérente. Consultez sa documentation pour plus d’informations.
django.utils.safestring est principalement utilisée par les fonctions mark_safe() et mark_for_escaping() qui n’ont elles-même pas changé. Dans le cas où vous utilisez ces structures internes, voici les modifications de noms effectuées :
Ancien nom |
Nouveau nom |
---|---|
EscapeString | EscapeBytes |
EscapeUnicode | EscapeText |
SafeString | SafeBytes |
SafeUnicode | SafeText |
Pour des motifs de rétrocompatibilité, les anciens noms fonctionnent encore avec Python 2. Avec Python 3, EscapeString et SafeString sont respectivement des alias de EscapeText et SafeText.
Pour des motifs de compatibilité ascendante, les nouveaux noms sont valables dès Django 1.4.2.
Méthodes __str__() et __unicode__()¶
Dans Python 2, le modèle d’objet spécifie les méthodes __str__() et __unicode__(). Si ces méthodes existent, elles doivent renvoyer respectivement str (octets) et unicode (texte).
L’instruction print et la fonction native str appellent __str__() pour déterminer une représentation humainement lisible d’un objet. La fonction native unicode appelle __unicode__() si elle existe, et se rabat sinon sur __str__() et décode le résultat avec le codage système. Inversement, la classe de base Model déduit automatiquement __str__() à partir de __unicode__() en codant le résultat en UTF-8.
Avec Python 3, il n’existe que __str__() qui doit renvoyer du contenu str (texte).
(Il est aussi possible de définir __bytes__(), mais les applications Django n’ont que peu l’utilité de cette méthode, parce qu’elles ont rarement affaire directement à des octets (bytes).)
Django propose une manière simple de définir les méthodes __str__() et __unicode__() qui fonctionnent à la fois avec Python 2 et 3 : vous devez définir une méthode __str__() renvoyant du texte et en lui appliquant le décorateur python_2_unicode_compatible().
Avec Python 3, le décorateur effectue une opération blanche. Avec Python 2, il définit les méthodes __unicode__() et __str__() appropriées (remplaçant la méthode __str__() originale dans le processus). Voici un exemple :
from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class MyClass(object):
def __str__(self):
return "Instance of my class"
Cette technique est le meilleur exemple de la philosophie de migration adoptée par Django.
Pour des motifs de compatibilité ascendante, ce décorateur est disponible dès Django 1.4.2.
Finalement, notez que __repr__() doit renvoyer du contenu de type str dans toutes les versions de Python.
Classes dict et classes affiliées¶
Les méthodes de dictionnaire dict.keys(), dict.items() et dict.values() renvoient des listes en Python 2 et des itérateurs en Python 3. Les classes QueryDict et autres classes de type dictionnaire définies dans django.utils.datastructures se comportent de la même manière en Python 3.
six fournit des fonctions de compatibilité pour prendre en compte cette modification : iterkeys(), iteritems() et itervalues(). Il contient également une fonction non documentée iterlists qui fonctionne bien pour django.utils.datastructures.MultiValueDict et ses sous-classes.
Objets HttpRequest et HttpResponse¶
Selon PEP 3333:
les en-têtes sont toujours des objets de type str,
les flux d’entrée et de sortie sont toujours des objets de type bytes (octets).
Plus précisément, HttpResponse.content contient des octets, ce qui peut poser problème quand on le compare avec un contenu str dans les tests. La solution privilégiée est de compter sur assertContains() et assertNotContains(). Ces méthodes acceptent une réponse et une chaîne unicode comme paramètres.
Lignes directrices pour le code¶
Les lignes directrices suivantes ont été appliquées dans le code source de Django. Elles sont aussi recommandées pour les applications tierces qui suivent la même stratégie de migration.
Contraintes de syntaxe¶
Unicode¶
En Python 3, toutes les chaînes sont considérées par défaut comme de l’unicode. Le type unicode de Python 2 est appelé str en Python 3 et l’ancien type str est devenu bytes.
Vous ne pouvez pas utiliser le préfixe u devant une chaîne littérale unicode parce que cela constitue une erreur de syntaxe en Python 3.2. Vous devez par contre préfixer les chaînes d’octets avec b.
Afin d’activer un comportement identique en Python 2, chaque module doit importer unicode_literals provenant de __future__:
from __future__ import unicode_literals
my_string = "This is an unicode literal"
my_bytestring = b"This is a bytestring"
Si vous avez besoin d’une chaîne littérale d’octets en Python 2 et que cette même chaîne littérale soit de l’unicode en Python 3, utilisez la fonction native str:
str('my string')
En Python 3, aucune conversion automatique n’est effectuée entre les types str et bytes, et le module codecs est devenu plus strict. str.encode() renvoie toujours du contenu de type bytes et bytes.decode du contenu de type str. Par conséquent, la structure suivante est parfois nécessaire :
value = value.encode('ascii', 'ignore').decode('ascii')
Soyez prudent si vous devez indexer des chaînes d’octets.
Exceptions¶
Lorsque vous interceptez des exceptions, utilisez le mot-clé as:
try:
...
except MyException as exc:
...
Cette ancienne syntaxe a été supprimée en Python 3 :
try:
...
except MyException, exc: # Don't do that!
...
La syntaxe pour repropager une exception avec une pile d’appel différente a aussi été modifiée. Utilisez six.reraise().
Méthodes magiques¶
Utilisez les modèles ci-dessous pour prendre en charge les méthodes magiques renommées en Python 3.
Itérateurs¶
class MyIterator(six.Iterator):
def __iter__(self):
return self # implement some logic here
def __next__(self):
raise StopIteration # implement some logic here
Évaluation booléenne¶
class MyBoolean(object):
def __bool__(self):
return True # implement some logic here
def __nonzero__(self): # Python 2 compatibility
return type(self).__bool__(self)
Division¶
class MyDivisible(object):
def __truediv__(self, other):
return self / other # implement some logic here
def __div__(self, other): # Python 2 compatibility
return type(self).__truediv__(self, other)
def __itruediv__(self, other):
return self // other # implement some logic here
def __idiv__(self, other): # Python 2 compatibility
return type(self).__itruediv__(self, other)
Ces méthodes spéciales sont recherchées au niveau de la classe, et non de l’instance, pour refléter le comportement de l’interpréteur Python.
Écriture de code compatible avec six¶
six est la bibliothèque de compatibilité incontournable pour prendre en charge à la fois Python 2 et 3 dans une même base de code. Lisez sa documentation !
Une version adaptée de six est incluse dans Django à partir de la version 1.4.2. Vous pouvez l’importer depuis django.utils.six.
Voici les modifications les plus fréquemment nécessaires pour écrire du code compatible.
Gestion des chaînes¶
Les types basestring et unicode ont été supprimés en Python 3, et la signification de str a changé. Pour tester ces types, utilisez les idiomes suivants :
isinstance(myvalue, six.string_types) # replacement for basestring
isinstance(myvalue, six.text_type) # replacement for unicode
isinstance(myvalue, bytes) # replacement for str
Python ≥ 2.6 dispose de l’alias bytes pour str, ce qui fait que vous n’avez pas besoin de six.binary_type.
long¶
Le type long n’existe plus en Python 3. 1L est une erreur de syntaxe. Utilisez le test six.integer_types pour savoir si une valeur est un nombre entier ou un nombre entier long :
isinstance(myvalue, six.integer_types) # replacement for (int, long)
xrange¶
Si vous utilisez xrange avec Python 2, importez six.moves.range et utilisez plutôt cette fonction. Il est aussi possible d’importer six.moves.xrange (qui est équivalent à six.moves.range), mais la technique précédente permet de simplement supprimer l’importation au moment de laisser tomber la prise en charge de Python 2.
La version adaptée de six dans Django¶
La version de six livrée avec Django (django.utils.six) contient quelques adaptations à usage purement interne.