Ca y est, on peut coder en Python 3


Python 3.4 vient de sortir, et avec cette release, je peux enfin recommander aux gens de se mettre à Python 3 plutôt que Python 2. Tout simplement parce que maintenant, en plus d’avoir énormément de libs qui ont migré, on a l’expérience de la bestiole. C’est stable, les perfs sont bonnes, la conversion de code d’une version à l’autre est bien documentée et maitrisée.

Et surtout, la 3.3 et la 3.4 viennent avec tout un tas des goodies super green.

Voici tout ce dont vous pouvez profiter avec Python 3 que vous ne pouvez pas faire avec Python 2.

Des classes classes

Les classes sont des new type classes en Python 3. Pas besoin d’hériter d’object:

# Au revoir !
class TouteMoche(object):
    pass
 
# Bonjour !
class PropreEtNette:
    pass

Les metaclasses ne se définissent plus comme un attribut spécial :

# Au revoir !
class TouteMoche(object):
    __metaclass__ = UnTruc
 
# Bonjour !
class PropreEtNette(metaclass=Untruc):
    pass

Super Super()

Franchement, qui se souvient de la syntaxe pour appeler proprement la méthode parente d’une classe ?

# Au revoir !
class FranchementHein(object):
 
    def __init__(self):
        super(FranchementHein, self).__init__()
 
# Bonjour !
class FranchementHein:
 
    def __init__(self):
        super().__init__()

Meta meta programmation

Le module inspect permet maintenant de récupérer des infos très très précises sur la signature des fonctions :

    >>> from inspect import signature
    >>> def foo(a, *, b:int, **kwargs):
    ...     pass
 
    >>> sig = signature(foo)
 
    >>> str(sig)
    '(a, *, b:int, **kwargs)'
 
    >>> str(sig.parameters['b'])
    'b:int'
 
    >>> sig.parameters['b'].annotation
    <class 'int'>

Et comme vous pouvez le voir, les annotations sont de la partie.

Les classes ont également un nouvel attribut __qualname__, qui, comme on peut s’y attendre, est le qualified name de la classe :

>>> class C:
...   def f(): pass
...   class D:
...     def g(): pass
...
>>> C.__qualname__
'C'
>>> C.f.__qualname__
'C.f'
>>> C.D.__qualname__
'C.D'
>>> C.D.g.__qualname__
'C.D.g'

Ca marche aussi pour les fonctions.

Enfin pour les gars qui sont vraiment tordus, on a une nouvelle méthode pour les metaclasses, __locallookup__, qui permet d’influencer le MRO à la volée.

Un yield qui yield

Bon, mon titre ne veut rien dire, c’était pour la continuité.

yield a maintenant un petit frère, yield from, qui permet de déléguer l’itération à un itérable sous jacent :

def generateur():
    yield from "123"
    yield from (str(x) for x in range(4, 7))
    yield from ("7", "8", "9")
 
for i in generateur():
    print(i)
 
## 1
## 2
## 3
## 4
## 5
## 6
## 7
## 8
## 9

Là ce n’est pas très utile, mais sur des générateurs complexes, c’est sympa. Surtout que yield from passe aussi les valeur avec send(), fait remonter les exceptions proprement et est détecté par les events loops du module asyncio, dont on parlera plus tard.

Batteries included, with charger

Pip et virtualenv sont livrés avec la 3.4 ! Rien de plus à faire, rien à installer ! Enfin !

Malheureusement pas virtualenv wrapper, mais c’est déjà pas mal.

Un nouveau format, le wheel, permet de faire des paquets binaires qui n’auront pas besoin d’être compilés sur la machine cible. Ca veut dire bientôt du numpy / scipy sans avoir à compiler quoi que ce soit. C’est un remplacement du egg plus simple et sans les problèmes de compatibilités. Il ne vise pas la distribution de projets Python standalones, comme le egg qui a notamment eu le problème de vouloir tout faire en même temps.

Pour ce dernier, le pyz est en discussion.

Enfin les scripts Python supportent maintenant l’équivalent du shebang, mais sous Windows. Ceci vient avec l’introduction de la commande py qui permet de lancer toutes les commandes Python (pip, venv, etc.) pour une version de Python installée spécifique.

Ah, non, pas “enfin”, j’ai oublié un truc :

python -X faulthandler votre_script.py vous permet maintenant d’obtenir une stacktrace sur les scripts utilisant ctypes \o/. Et le module tracemalloc permet d’enquêter sur l’allocation mémoire.

Tout un tas de trucs que les codeurs Java vont adorer

Il y a des Enums, et ça vient sous toutes les formes :

from enum import Enum, IntEnum
 
# La version de feignasse :
Animal = Enum('ant bee cat dog')
 
# La version 'anal retentive'
class Animal(IntEnum):
     ant = 1
     bee = 2
 
# et tout ce qu'il faut entre les deux pour les centristes

Pour les nostalgiques de l’overloading, on peut maintenant spécifier que le code d’une fonction est différent selon le type des arguments qui lui sont passés :

from functools import singledispatch
 
@singledispatch
def fun(arg):
    print('Comportement par défaut, teddy')
 
@fun.register(int)
def _(arg):
    print("Tu m'as passé un int, jonhy !")
 
class Cacatoes:
    pass
 
@fun.register(Cacatoes)
def _(arg):
    print("Tu m'as passé un Cacatoes, billy !")
 
 
fun('Do')
fun([])
fun(1)
fun(Cacatoes)
 
## Comportement par défaut, teddy
## Comportement par défaut, teddy
## Tu m'as passé un int, jonhy !
## Tu m'as passé un Cacatoes, billy !

Et puis le module abc pour faire des classes abstraites, même si ça a été backporté en Python 2.7, donc un peu hors sujet.

Les built-in ont un peu changé

Pour le texte, c’est de l’unicode partout, avec utf8 par défaut pour les conversions. Ca veut dire plus de u devant les chaînes de caractère, plus de déclaration de l’encoding en haut des fichiers de code. Moins de decode / encode.

Mais ça veut dire aussi que open a besoin obligatoirement d’un paramètre encoding, dont la valeur par défaut est bien entendu utf8.

Attention cependant, le built-in bytes n’est pas l’exacte équivalent du type str en Python 2 puisqu’il ne possède plus certaines méthodes de manipulation de texte comme replace:

>>> "Trololo".replace('o', 'a')
'Tralala'
>>> b"Trololo".replace('o', 'a')
Traceback (most recent call last):
  File "<ipython-input-5-f7e93d6f629e>", line 1, in <module>
    b"Trololo".replace('o', 'a')
TypeError: expected an object with the buffer interface

C’est un peu chiant quand on manipule des protocoles binaires, et les mecs de mercurial ont un peu gueulé. Donc il est possible que ça change. J’ai mis beaucoup de fois le mot “peu” dans cet article, ce qui est stylistiquement très laid. Mais je suis trop paresseux pour éditer cet article qui fait maintenant 3 km.

Pour les nombres, on n’a plus à se soucier du type long, qui n’existe plus. / est maintenant la division ordinaire et // la division entière.

Pour les fonctions built-in, pas mal de changements avec bien entendu, print() qui devient une fonction, mais aussi toute ce qui est zip, map, etc, qui retournent des générateurs au lieu de listes, tout comme dict.items et consorts. Ah oui, et import est maintenant absolu par défaut.

Quelques libs en plus

Marre du module os ? pathlib permet de donner un petit goût d’objet à vos manipulations de FS, comme path.py. Ca reste moins bien, mais c’est mieux que rien.

Raz-le bol de recoder la fonction moyenne, médiane, etc ? Le module statistics a été ajouté pour ça.

Enfin, le fameux module mock, qui permet de simuler tout un tas de trucs sans tout casser :

>>> from mock import patch
>>> with patch.object(os, 'listdir', return_value=['file2.txt', 'file2.text']):
    print(os.listdir('/etc/'))
...
[u'file2.txt', u'file2.text']
>>> os.listdir('/etc/')[:2]
[u'environment', u'hosts.allow']

Tout est bien rangé

Les fichiers bytecode Python sont maintenant tous groupés dans un dossier appelé __pycache__. Finis les .pyc qui trainent partout. Et en plus prefixés de l’architecture avec laquelle ils ont été générés. Toujours utile.

En prime pas mal de noms ont été normalisés : tous les modules sont maintenant en minuscule, tous les modules liés à IO sont groupés dans le module io, urllib 1 et 2 ont été mergés, et l’arborescence des exceptions pour les erreurs d’IO a maintenant beaucoup plus de sens :

# Au revoir !
from errno import ENOENT, EACCES, EPERM
 
try:
    with open("document.txt") as f:
        content = f.read()
except IOError as err:
    if err.errno == ENOENT:
        print("document.txt file is missing")
    elif err.errno in (EACCES, EPERM):
        print("You are not allowed to read document.txt")
    else:
        raise
 
# Bonjour !
 
try:
    with open("document.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("document.txt file is missing")
except PermissionError:
    print("You are not allowed to read document.txt")

Asyncio, la prog asynchrone rebootée

Une des raisons pour laquelle je tape sur javascript aussi fort, c’est aussi la jalousie. Ils ont tous les derniers joujoux asynchrones hi-tech, meh !

Avec Python on était obligé d’installer tornado ou twisted pour ça, et c’était un peu dommage :)

Avec la 3.4, la prog asynchrone fait peau neauve (adieu l’horrible module asyncore) et propose de la prog asynchrone plus simple, plus légère, plus facilement intégrable : asyncio.

Si vous lisez ce PEP, ça ne va pas vous parler, alors je vais potasser tout ça et faire un petit article.

Mon seul regret avec asyncio, c’est que c’est un module uniquement bas niveau, alors que tulip, le prototype de la lib, contenait des mobiles haut niveau comme par exemple un client http bien foutu. Du coup ça a donné ce genre de discussion sur le blog, avec des gens qui ne comprenaient pas que je m’attendais à un peu de haut niveau et eux qui ne voyaient que le bas, et donc s’attardaient sur de la sémentique d’implémentation de la prog non blocante.

En conclusion

Le code Python 3 est plus court, plus cohérent, avec moins de surprises, moins de dépendances externes et plus de flexibilité. Des comportements par défaut sains un peu partout, des outils en plus, et comme d’habitude vous n’en utiliserez pas la moitié tellement il y a à faire.

Python 3.4 est vraiment un très bon cru, honnêtement, avec toutes ces améliorations, le langage parvient à se maintenir au niveau des petits jeunes et de leur hype, sans sacrifier sa solidité et sa cohérence.

C’est pour ça qu’on a attendu 5 ans. C’est pour ça que la migration a été lente et prudente.

Parce que la communauté Python fait vraiment les choses bien.

Les exemples du blogs seront donc à partir de maintenant en Python 3.

38 thoughts on “Ca y est, on peut coder en Python 3

  • entwanne

    Petite erreur dans le lien vers le module tracemalloc, qui est un lien relatif et donc ne mène pas vers la bonne page.
    Aussi, le lien vers la discussion asynchio mène vers une page admin, qui n’est donc pas lisible.

  • Zanguu

    En première lecture, 3 erreurs :
    Erreur dans l’exemple de l’overloading
    fun(Cacatoes) devrait retourner :
    ## Tu m'as passé un Cacatoes, billy ! (si j’ai bien suivi. Sinon je veux bien une explication).

    Dans les built-in
    Maid je suis trop paraisseux > Mais je suis trop paresseux

    Enfin Asyncio, le lien “ce genre de discussion” pointe sur l’admin (moi je veux bien aller voir mais j’ai pas les logs =D)

  • Zanguu

    Et pour que mon chiffre de 3 erreurs soit bon sans compter le doublon de entwanne, dans la partie Asyncio le lien vers “asyncore” pointe vers la racine du blog.

  • Romain

    TRES très bon tout ça. Je pense que je vais m’y mettre aussi.

    +1 pour super() juste incompréhensible en python 2.

  • Tiger-222

    Nice tout ça ! ^^
    Quelques fautes dans “Asyncio, la prog asynchrone rebootée”, le lien vers “ce genre de discussion” redirige vers l’édition du commentaire souhaité. Et juste avant, “Avec Python ont été obligé d’installer tornado” => “Avec Python on était”, non ?

    Dans “En conclusion”, “et comme d’habitude pour n’en utiliserait pas la moitié” => “vous n’en utiliserez”.

  • Sam Post author

    @François : bonne nouvelle !

    @autres : merci, je corrige !

  • Anb

    Et fatigué aussi : “Maid je suis trop paraisseux pour éditer cet article qui fait maintenant 3 km.”

    un ‘s’ à ‘Mais’, et surtout : ‘paresseux’ :)

    Merci pour cette synthèse en tout cas, je vais pouvoir prosel avec de vrais arguments ^^

  • pouete

    @Anne Onyme Le probleme entre django et python 3 n’est pas django , mais les apps que tu vas installer qui elles, risques de ne pas etre compatibles python3 :)

  • Zanguu

    Question quand même, pourquoi super garde ses parenthèses ?
    Serait ce envisageable un “super.” un peu comme le “base.” de C# ou le “parent::” de php ?

    Du coup je me répond tout seul je crois :
    Quand la classe hérite de plusieurs autres classes, comment on lui indique la classe parente à utiliser ?

  • groug

    Un très bon résumé, pour les flemmards, c’est parfait, merci !

  • Sam Post author

    @Zanguu: oui tu te réponds tout seul. Super doit être une fonction pour attendre une classe en paramètre au besoin.

    super() n’a pas vraiment changé en Python 3, c’est juste que interpréteur python le remplace automatiquement par super(NomClasse, self). C’est un simple search/replace :)

    Du coup on peut mettre une classe parente à la place de NomClasse, et en fait, on peut même appeler super en dehors d’une classe.

  • Teocali

    Putain, moi qui hesitait entre Groovy et Python pour mon prochain language de script, ca va etre encore plus dur.
    D’ailleurs, question : One te voit taper a droite (java script) et a gauche (Ruby), plus ou moins gentiment, mais jamais sur Groovy ? une raison ?

  • Sam Post author

    Groovy n’est pas vraiment un compétiteur : un mec qui aime Python peut faire du Python et du Java mélangé en utilisant jython, donc c’est un peu comme si il n’était pas sur le radar :)

  • Teocali

    Certes, mais pour un mec comme moi qui n’a pas d’opinion, t’en penses quoi en terme de feature ?

  • Teocali

    wikipedia l’explique mieux que moi

    http://fr.wikipedia.org/wiki/Groovy_(langage)

    Perso, le peu que j’ai teste (au sein de scripts gradle pour un gros projet) m’a beaucoup plus. Et comme je commence serieusement a me mettre serieusement a la prog Android et que gradle (un DSL base sur Groovy) est l’outil de build et de deploiement “officiel” pour android, j’avoue que je me tate.

    • Sam Post author

      Pour la prog mobile, Python est possible avec kivy, mais un langage plus proche de java aura toujours un avantage sur android.

  • Sam Post author

    @Teocali: ça rempli très bien son rôle de langage de scripting pour la plateforme java. C’est syntaxiquement assez agréable, mais ça manque d’outil comparé à la compétition et est limité à la JVM.

  • Morgotth

    Ce billet me rappelle que j’ai la chance de faire du Python 3 depuis 2 ans 1/2 à mon travail et j’ai donc découvert les subtilités de Python 2 après celle de Python 3 … Façon bienvenue à la préhistoire :3

  • gontran

    Faudrait faire quelque chose pour règler votre cache. De plus en plus, je me fais servir la versin mobile du site….

  • Teocali

    @Sam : Pour la prog mobile, j’attends de voir. pour le moment y’a pas l’air d’avoir de projet serieux (Java ou pas, ce qui compte, c’est le bytecode et la JVM utilisee qui compte), mais ca ne saurait tarder, je pense, because gradle.

  • Sam Post author

    @gontran: on a précisé qu’on ne corrigerait pas ce bug. Faudrait faire une FAQ.

  • albert

    Moi, tant qu’il n’y a pas numpy, pyfits et matplotlib, ça ne me sert à rien et je reste calé sur 2.7…

  • LeNaif

    Avec la version 3.4 on a bien le module asyncio, ok. Mais est-ce que le reste des modules est prêt à être utiliser de manière asynchrone ? Si non, je comprends pourquoi node.js a du succès.

  • Sam Post author

    Il y en a pas mal a migrer dans la lib standard. Mais on peut faire de l’asynchrone en Python avec twisted depuis bien avant que nodejs ait été créé, avec toutes les libs nécessaires (accès base de données notamment). Je dirais que le succès nodejs a été ailleurs, car son succès à commencé avant qu’il ait eu plein de libs.

  • Sébastien

    Bonjour,

    Il semblerait qu’il faille lire :

    func(Custom())

    Au lieu de :

    func(Custom)

    Bonne continuation.

  • Papyfouette

    Bonjour,

    Petite question à laquelle j’attends une petite réponse du coups :P

    Peut-on créer une fonction et faire appel à une class à l’intérieur de celle-ci ? (la classe se trouvant dans un autre fichier que j’importe via un from fichier import class)

    Et si oui (j’espère que c’est oui), comment peut-on procéder ainsi sans utiliser une méthode crade.

    Je souhaite mettre chaque class/def dans des fichiers séparé pour alléger mon code, est-ce une bonne solution ?

    Excusez-moi, si ça sort du thème :3

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.