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.
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.
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 ».
Python 2’s unicode() type was renamed str() in Python 3, str() was renamed bytes(), and basestring() disappeared. six provides tools to deal with these changes.
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.
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).
The print statement and the str built-in call __str__() to determine the human-readable representation of an object. The unicode() built-in calls __unicode__() if it exists, and otherwise falls back to __str__() and decodes the result with the system encoding. Conversely, the Model base class automatically derives __str__() from __unicode__() by encoding to 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.
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.
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.
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.
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"
If you need a byte string literal under Python 2 and a unicode string literal under Python 3, use the str builtin:
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.
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().
Utilisez les modèles ci-dessous pour prendre en charge les méthodes magiques renommées en Python 3.
class MyIterator(six.Iterator):
def __iter__(self):
return self # implement some logic here
def __next__(self):
raise StopIteration # implement some logic here
class MyBoolean(object):
def __bool__(self):
return True # implement some logic here
def __nonzero__(self): # Python 2 compatibility
return type(self).__bool__(self)
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.
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.
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.
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)
If you use xrange on Python 2, import six.moves.range and use that instead. You can also import six.moves.xrange (it’s equivalent to six.moves.range) but the first technique allows you to simply drop the import when dropping support for Python 2.
The version of six bundled with Django (django.utils.six) includes a few customizations for internal use only.
Jan 13, 2016