Je mets la touche finale à un programme de sauvegarde dédupliqué d'environ 5000 lignes ( http://stromberg.dnsalias.org/~strombrg/backshift/ ) qui fonctionne sur CPython 2.[567], CPython 3.[0123] (3.3 est encore alpha 0), Pypy 1.7 et Jython trunk. J'ai également essayé IronPython, mais c'était une chose assez différente - il n'avait pas de bibliothèque standard, donc pas de backshift love. Oh, et il peut utiliser Cython pour sa boucle la plus interne, ou psyco - mais pypy est plus rapide que les deux, surtout sur les systèmes 32 bits.
Quoi qu'il en soit, j'ai découvert que pour écrire du code qui fonctionne aussi bien sous 2.x que sous 3.x, il suffisait de faire ce qui suit :
1) print(variable) fonctionne de la même manière sur 2.x et 3.x. print(variable1, variable2) ne fonctionne pas. En 2.x, print(variable) dit "évalue cette expression entre parenthèses, et imprime le résultat unique en utilisant l'instruction print". En 3.x, print(variable) dit "appeler la fonction print sur ce résultat unique". Donc print('abc %d %d' % (1, 2)) fonctionne bien dans les deux cas, car il s'agit d'un résultat à valeur unique, et les deux utilisent l'opérateur % pour le formatage des chaînes.
2) Évitez les constantes octales. Au lieu d'écrire 0755, écrivez (7*64 + 5*8 + 5).
3) Pour faire des E/S binaires dans l'un ou l'autre, j'ai utilisé mon module bufsock. http://stromberg.dnsalias.org/~strombrg/bufsock.html J'ouvrirais un fichier avec os.open, et l'envelopperait avec bufsock (ou utiliserait la classe rawio dans le module). Sous 2.x, cela renverrait une chaîne d'octets codée en chaînes de caractères 8 bits. Sous 3.x, cela renvoie un objet bytes, qui agit comme une liste de petits entiers. Ensuite, j'ai simplement fait passer l'un ou l'autre, en testant avec "isinstance(foo, str)" si nécessaire pour distinguer les deux. Je l'ai fait parce que pour un programme de sauvegarde, les octets sont des octets - je ne voulais pas m'embrouiller avec des encodages qui pourraient nuire à la fiabilité de la sauvegarde des données, et tous les encodages ne font pas un bon aller-retour.
4) Lorsque vous faites des exceptions, évitez le mot clé "as". Utilisez plutôt EG :
try:
self.update_timestamp()
except (OSError, IOError):
dummy, utime_extra, dummy = sys.exc_info()
if utime_extra.errno == errno.ENOENT:
5) Un certain nombre de modules ont été renommés lors de la transition de 2.x à 3.x. Essayez donc d'importer l'un ou l'autre dans un module autrement vide, avec quelque chose comme :
try:
from anydbm import *
except ImportError:
from dbm import *
...cela apparaîtrait dans un module à part entière, avec un nom EG adbm.py. Ensuite, chaque fois que j'ai besoin d'un magasin de valeurs clés, j'importe adbm au lieu des deux choses différentes nécessaires pour 2.x ou 3.x directement. Ensuite, j'ai pylint tout sauf ce module stubby, adbm.py - et les choses comme lui que pylint n'aimait pas. L'idée était de pylindre tout ce qui était possible, avec des exceptions à la règle "tout doit être pylint" dans un petit module à part, une exception par module.
6) Il est très utile de mettre en place des tests unitaires automatiques et des tests système qui fonctionnent sur 2.x et 3.x, puis de tester fréquemment sur au moins un interpréteur 2.x ainsi que sur au moins un interpréteur 3.x. J'exécute aussi souvent pylint sur mon code, mais seulement un pylint qui vérifie la conformité à la version 2.5.x - j'ai commencé le projet avant que pylint n'obtienne le support de la version 3.x.
7) J'ai mis en place un petit module "python2x3" qui a quelques constantes et appelables pour faciliter la vie : http://stromberg.dnsalias.org/svn/python2x3/trunk/python2x3.py
8) Les littéraux "b" ne fonctionnent pas en 2.5, bien qu'ils fonctionnent plus ou moins en 2.[67]. Au lieu d'essayer de prétraiter ou quelque chose, j'ai mis en place un constants_mod.py qui avait beaucoup de choses qui seraient normalement des littéraux b'' en 3.x, et les a convertis d'une simple chaîne de caractères à ce que le type "bytes" est pour 2.x ou 3.x. Ainsi, ils sont convertis une fois à l'importation du module, et non pas à plusieurs reprises au moment de l'exécution. Si vous visez 2.[67] et plus, il y a peut-être une meilleure façon de faire, mais quand j'ai commencé, le projet Pypy n'était compatible qu'avec 2.5, et Jython l'est toujours.
9) En 2.x, les entiers longs ont un suffixe L. En 3.x, tous les entiers sont longs. J'ai donc décidé d'éviter autant que possible les constantes d'entiers longs ; 2.x fera passer un entier en entier long si nécessaire, donc cela semble bien fonctionner pour la plupart des choses.
10) Il est très utile de disposer d'un certain nombre d'interpréteurs python pour faire des tests. J'ai construit 2.[567] et 3.[0123] et les ai stockés dans /usr/local/cpython-x.y/ pour faciliter les tests. J'ai également mis quelques Pypy et Jython dans /usr/local, toujours pour faciliter les tests. Avoir un script pour automatiser les constructions de CPython était plutôt précieux.
Je crois que ce sont toutes les contorsions que j'ai dû faire pour obtenir une base de code python hautement portable dans un projet non trivial. La seule grande omission dans la liste que j'ai écrite ci-dessus, c'est que je n'essaie pas d'utiliser des objets unicode - c'est quelque chose que quelqu'un d'autre est probablement mieux qualifié pour commenter.
HTH