Passage à Python 3Lien vers cette rubrique
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.
PhilosophieLien vers cette rubrique
This document assumes that you are familiar with the changes between Python 2 and Python 3. If you aren’t, read Python’s official porting guide first. Refreshing your knowledge of unicode handling on Python 2 and 3 will help; the Pragmatic Unicode presentation is a good resource.
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 migrationLien vers cette rubrique
Chaînes littérales unicodeLien vers cette rubrique
Cette étape consiste à :
Ajouter
from __future__ import unicode_literalsau 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
udevant toutes les chaînes unicode.Ajouter un préfixe
bdevant 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.
Gestion des chaînesLien vers cette rubrique
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 |
|---|---|
|
|
|
|
|
|
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.
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 |
|---|---|
|
|
|
|
|
|
|
|
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__()Lien vers cette rubrique
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éesLien vers cette rubrique
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 HttpResponseLien vers cette rubrique
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 codeLien vers cette rubrique
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 syntaxeLien vers cette rubrique
UnicodeLien vers cette rubrique
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.
ExceptionsLien vers cette rubrique
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 magiquesLien vers cette rubrique
Utilisez les modèles ci-dessous pour prendre en charge les méthodes magiques renommées en Python 3.
ItérateursLien vers cette rubrique
class MyIterator(six.Iterator):
def __iter__(self):
return self # implement some logic here
def __next__(self):
raise StopIteration # implement some logic here
Évaluation booléenneLien vers cette rubrique
class MyBoolean(object):
def __bool__(self):
return True # implement some logic here
def __nonzero__(self): # Python 2 compatibility
return type(self).__bool__(self)
DivisionLien vers cette rubrique
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 sixLien vers cette rubrique
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înesLien vers cette rubrique
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.
longLien vers cette rubrique
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)
xrangeLien vers cette rubrique
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.
Modules déplacésLien vers cette rubrique
Certains modules ont été renommés dans Python 3. Le module django.utils.six.moves (basé sur le module six.moves) constitue un emplacement d’importation compatible.
PY2Lien vers cette rubrique
Si vous avez besoin de différencier le code entre Python 2 et Python 3, testez six.PY2:
if six.PY2:
# compatibility code for Python 2
Il s’agit d’une solution de dernier recours lorsque six ne fournit pas de fonction correspondante.
La version adaptée de six dans DjangoLien vers cette rubrique
La version de six livrée avec Django (django.utils.six) contient quelques adaptations à usage purement interne.