Développeur Freelance ou Pas, Autheur O'Reilly et Conférencier.
Mon crédo : les langages de programmation ne sont que des outils; plus on en a dans sa boîte, mieux on est équipé pour répondre aux problématiques du client.
Je m'efforce donc de pratiquer un ensemble de langages de programmation complémentaires. Java depuis 1996, Python depuis 1998, Ruby depuis 2005 et Go depuis 2014.
http://linkedin.com/in/casabianca
Le titre de la première version de cette présentation, qui datait de 2015, était : Faut-il Passer à Python 3 ? La question ne se pose plus car la fin de vie de python 2 est programmée au le 1er janvier 2020.
Si vous n'avez pas migré vos projets critiques, il est urgent de le faire car après cette date il n'y aura plus de mise à jour de sécurité pour Python 2. Si votre projet a de fortes contraintes de sécurité et doit être mis à jour régulièrement, vous exposerez vos utilisateurs à de gros soucis.
Si de plus vous êtes tenu par des normes de sécurité, comme PCIDSS qui impose des mises à jour de sécurité régulières, vous risquez de gros ennuis.
Python 3 (ou Python 3000 ou encore Py3k) est une refonte de Python, incompatible avec les versions 2, qui a commencé le 5 avril 2006 (avec le PEP 3000) et qui a abouti à la release de Python 3.0.0 le 3 décembre 2008.
Depuis, nombre de fonctionnalités ont été backportées vers les versions 2 (accessible avec les imports from __future__
).
Un document liste les changements de Python 3 :
https://docs.python.org/3.8/whatsnew/3.0.html
Print n'est plus une déclaration mais une fonction :
# avant
print "Hello World!"
# après
print("Hello World!")
Les formes les plus bizarres ont été rationalisées :
# avant
print "pas de retour à la ligne",
print >>sys.stderr, "erreur"
print ("spam", "eggs")
# après
print("pas de retour à la ligne", end=" ")
print("erreur", file=sys.stderr)
print(("spam", "eggs"))
On peut utiliser ces nouvelles formes dans un source Python 2 avec l'import from __future__ import print_function
. L'outil 2to3 est capable de réaliser la conversion automatiquement pour vous.
En Python 2, dict.keys()
, dict.values()
et dict.items()
renvoient une liste. Cette liste est valide au moment de l'appel et si le dictionnaire est modifié, la liste ne l'est pas. D'autre part, cette liste est modifiable.
En Python 3, ces appels renvoient des vues (views en anglais). Ces vues reflètent l'état du dictionnaire, même après modification. D'autre part, ces vues ne sont pas modifiables.
On peut voir la différence entre listes et vues de la manière suivante :
Les fonctions map()
, filter()
et zip()
renvoient aussi des vues. Pour les deux premières, il faut les remplacer par des list comprehensions :
import math
# avant
print map(math.factorial, range(6))
print filter(lambda x: x % 2 == 0, range(6))
# après
print([math.factorial(x) for x in range(6)])
print([x for x in range(6) if x % 2 == 0])
Ces formes avec list comprehensions sont bien plus claires et tout aussi rapides que les versions avec fonctions.
En Python 2, les chaînes de caractères sont de deux types : unicode et 8-bits. On peut mixer les deux, mais cela conduit souvent à des erreurs de type UnicodeDecodeError
.
En Python 3, le type qui contient du texte est str et celui qui contient de la donnée est bytes. On ne peut mixer les deux, sans quoi on lève une TypeError
.
On peut préparer la migration en n'utilisant en Python 2 que des chaînes unicode pour contenir du texte et effectuer explicitement les conversions entre str
et bytes
. Alors l'outil 2to3 pourra effectuer la majeure partie du travail pour vous.
Il y aurait beaucoup à dire sur le sujet des chaînes de caractères et des données binaires, pour plus de détails sur ce sujet, voir le Unicode Howto : https://docs.python.org/3.5/howto/unicode.html.
- Comparaisons : on ne peut plus comparer des types qui ne sont pas comparables, donc
1 < ''
est interdit en Python 3 (lève uneTypeError
). - Nombres entiers : tous les entiers sont maintenant du type
long
. Une division de nombres entiers comme1/2
donne unfloat
, pour effectuer une division entière on écrira1//2
.
- Annotations des arguments et valeurs de retour des fonctions.
def repeat(s: str, n: int) -> str:
return n*s
- Arguments de fonction ne pouvant qu'être nommés.
>>> def func(*, foo, bar):
>>> print(foo, bar)
>>> func(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes 0 positional arguments but 2 were given
- Déclaration
nonlocal
qui permet d'assigner à une variable qui est dans une portée externe sans être globale.
- Éclatement d'itérable avec reste :
>>> x, y, *reste = range(5)
>>> print(reste)
[2, 3, 4]
A noter que *reste
peut être placé ailleurs dans la liste des variables.
- Compréhension de dictionnaire :
{65+c: chr(65+c) for c in range(26)}
Construira un dictionnaire associant une lettre majuscule à son code ASCII.
- Expression pour un set :
>>> s = {1, 2, 2, 3, 3}
>>> print(s)
{1, 2, 3}
- Nouvelle syntaxe pour les valeurs octales : on écrira 0o720 au lieu de 0720, qui a disparu.
- Nouvelle syntaxe pour les valeurs binaires : on pourra écrire 0b1010 par exemple. Il y a une nouvelle fonction
bin()
pour convertir un nombre entier en sa représentation en binaire. - Les valeurs de
bytes
commencent pas un b ou un B . Il y a une nouvelle fonctionbytes()
pour convertir un entier en sa représentation en une liste d'octets.
- Exceptions avec causes :
raise Exception("exception") from Exception("cause")
# équivalent à
e = Exception("exception")
e.__cause__ = Exception("cause")
raise e
- Capture des exceptions, on doit écrire :
# avant
except Exception, e:
...
# après
except Exception as e:
...
- Une méta-classe n'est plus un attribut
__metaclass__
d'une classe :
class C(metaclass=M):
...
- Les antiquotes doivent être remplacées par
repr()
. - Les
<>
doivent être remplacés par!=
. - Le mot clé
exec
est remplacé par la fonctionexec()
. - On ne peut plus ajouter un
l
ou unL
à la fin des entiers (car tous les entiers sont des longs). - On ne peut plus placer
u
ouU
devant un chaîne (car toutes les chaînes sont Unicode). - La syntaxe
from module import *
n'est plus autorisée dans les fonctions, mais seulement au niveau module. - La seule syntaxe admise pour les imports relatifs est
from .module import name
. Tous lesimport from
non précédés d'un point sont considérés comme absolus. - Les classes Classic (ou de style ancien) n'existent plus.
- Le formatage des chaînes avec l'opérateur
%
ne sera plus supporté dans une future version et est remplacé avantageusement par les f-strings du typef"Hello {name}!"
.
- Le concept d'unbound method a été abandonné et remplacé par de simples fonctions.
- On peut enfin invoquer
super()
sans argument et cela renvoie la classe ou l'instance parente. - La fonction
raw_input()
a été renomméeintput()
. Pour retrouver l'ancien comportement, on écriraeval(input())
. - Le type
file
n'existe plus. dict.has_key()
n'existe plus, utiliser l'opérateurin
à la place.
- Des bibliothèques ont été abandonnées par manque d'utilisateurs ou suite à l'arrêt du support de plateformes (comme BeOS ou MacOS9).
- Des bibliothèques ont été renommées pour adhérer au PEP 0008 :
ConfigParser
par exemple a été renommé enconfigparser
. - Les bibliothèques mixtes (avec version pure Python et implémentée en C) ont été fusionnées de manière à ce que l'utilisateur n'aie pas à choisir la version utilisée.
- Des bibliothèques ont été regroupées : par exemple urllib, urllib2, urlparse et robotparse ont été regroupées dans urllib.
- D'autres bibliothèques enfin ont été nettoyées, comme sys par exemple.
Avant d'entreprendre la migration, il faut se poser les questions suivantes :
- Toutes les bibliothèques que j'utilise ont-t-elles été portées vers Python 3 ?
- Souhaite-t-on garder une compatibilité avec Python 2 ?
- Si oui, veut-on releaser :
- Une seule version compatible avec Python 2 et 3 simultanément
- Deux versions distinctes : une pour Python 2 et une pour 3
Maintenant que le support pour Python 2 va cesser dans quelques semaines, il me semble qu'une version compatible n'est plus indispensable, sauf cas particulier rare.
Du code compatible avec les deux versions est plus facile à maintenir, mais le code n'est pas très élégant.
Pour que la migration se passe dans de bonnes conditions, il est très utile de préparer le terrain en amont en prenant les mesures suivantes :
- S'assurer que toutes les dépendances sont compatibles Python 3 (avec caniusepython3 par exemple)
- Valider son code avec Pylint
- S'assurer que le code est bien couvert par des tests unitaires
- Des tests d'intégration sont très utiles, si possible automatisés, à défaut sous forme de cahiers de tests
- Rapprocher son code Python 2 le plus possible du 3 :
- Utiliser les features Python 3 à l'aide des from __future__ import ...
- N'utiliser que des chaînes Unicode et réaliser des conversions explicites avec des chaînes 8 bits
- Corriger les warnings levés par l'option -3 de Python (qui indique les features enlevées ou modifiées dans Python 3)
Pour réaliser la migration, on peut utiliser les outils suivants :
-
from __future__ est une série d'imports (division, absolute_import, print_function et unicode_literals) qui permettent d'utiliser des fonctionnalités Python 3 dans un source Python 2.
-
-3 est une option de la commande Python qui lève des warnings pour le code Python 2 qui ne peut être automatiquement converti vers 3 avec l'outil 2to3.
-
2to3 est un outil qui réalise la transformation d'un source Python 2 vers 3. Il faut cependant avoir réalisé un travail préparatoire, en particulier sur les chaînes de caractère.
-
six est une bibliothèque permettant de réaliser du code compatible Python 2 et 3.
-
futurize est un outil qui automatise le support de Python 2 et 3 simultanément
- S'assurer, avant la migration, que l'on a une bonne couverture de tests.
- Portage vers Python 2.7.
- Activer les warnings Python 3 avec l'option -3.
- Tester et éditer jusqu'à ce qu'il ne reste plus de warning.
- Utiliser l'outil 2to3 pour convertir le source vers Python 3. Ne pas éditer les sources résultants à la main !
- Tester le code avec Python 3.
- Si des problèmes persistent, corriger le source Python 2 et reprendre à partir de l'étape 4.
- On releasera deux versions : une Python 2 et une Python 3.
Il est conseillé de ne pas éditer le code Python 3 tant que l'on assure le support de la version Python 2.
Il est conseillé de ne pas faire de code compatible Python 2 et Python 3, mais de maintenir un source Python 2 capable de migrer automatiquement vers Python 3 avec l'outil 2to3. On distribuera alors deux versions, compatibles avec une version de Python.
L'adoption de Python 3 a été lente au début mais a fortement accéléré ces deux dernières années (d'après un sondage effectué par JetBrains fin 2018):
Le site py3readiness.org indique que les 360 bibliothèques les plus utilisées ont été à ce jour portées vers Python 3 :
S'il semble maintenant évident qu'il faut migrer sans tarder les projets vers Python 3, il ne faut pas négliger le coût de la migration :
- Le travail préparatoire est important
- Les outils de migration ne réalisent pas tout le travail