Licence CC BY

Sortie de Python 3.6

Tour d'horizon de la dernière version du célèbre langage de programmation

L’auteur de ce contenu recherche un rédacteur. N’hésitez pas à le contacter par MP pour proposer votre aide !

Nous vous parlions il y a un an de la sortie de la version 3.5 du langage Python. Cette version portait notamment sur la programmation asynchrone (avec les mots-clés async et await) et l’unpacking (opérateurs splat : * et **).

Les mois ont passé et arrivent aujourd’hui Python 3.6 (nouvelle version du langage) et CPython 3.6 (interpréteur officiel écrit en C). Cette version ajoute au langage l’interpolation de chaînes, les arguments nommés ordonnés, et quelques subtilités sur la création de classes. Des fonctionnalités secondaires, mais attendues depuis un certain temps par les développeurs Python.

Cet article a pour but de faire le tour de cette nouvelle version et de vous présenter les changements qu’elle apporte.

TL;DR - Résumé des principales nouveautés

Comme pour la version précédente, commençons par un résumé des principales nouveautés. Les fonctionnalités listées ici seront bien sûr détaillées par la suite. Pour rappel, les PEP sont des spécifications décrivant les potentielles fonctionnalités du langage.

  • PEP 498 : les chaînes de caractères peuvent maintenant être préfixées du symbole f pour être interpolées en fonction des variables du scope courant.

    Cette fonctionnalité reprend globalement la syntaxe supportée par la méthode format des chaînes de caractères.

    1
    2
    3
    >>> name = 'John'
    >>> f'Hello {name}!'
    'Hello John!'
    
  • PEP 468 : les arguments nommés reçus par une fonction sont maintenant assurés d’être ordonnés. Le paramètre spécial **kwargs d’une fonction correspondra alors toujours à un dictionnaire ordonné des arguments nommés.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    >>> def func(**kwargs):
    ...     for name, value in kwargs.items():
    ...         print(name, '->', value)
    ...
    >>> func(a=1, b=2, c=3)
    a -> 1
    b -> 2
    c -> 3
    >>> func(b=2, c=3, a=1)
    b -> 2
    c -> 3
    a -> 1
    
  • PEP 519 : ajout d’un protocole pour les chemins de fichiers. Il devient maintenant plus facile de manipuler des chemins et d’interagir avec les fonctions système.

    1
    2
    3
    4
    5
    6
    7
    >>> import pathlib
    >>> path = pathlib.Path('/home')
    >>> path / 'john'
    PosixPath('/home/john')
    >>> with open(path / 'john' / '.python_history') as f:
    ...     history = f.read()
    ...
    
  • PEP 520 : le dictionnaire de définition d’une classe devient lui aussi ordonné. L’ordre de définition des attributs et méthodes des classes est donc conservé.

    1
    2
    3
    4
    5
    6
    7
    >>> class A:
    ...     x = 0
    ...     y = 0
    ...     z = 0
    ...
    >>> A.__dict__
    mappingproxy({..., 'x': 0, 'y': 0, 'z': 0, ...})
    
  • PEP 487 : une nouvelle méthode (__init_subclass__) permet d’interférer depuis une classe mère sur la création de ses filles ; et les descripteurs sont informés lorsqu’ils sont assignés à un attribut de la classe, via leur méthode __set_name__.

  • PEP 525 : il devient possible d’écrire des générateurs asynchrones. Ils fonctionneront comme des générateurs habituels, à utiliser depuis d’autres coroutines.

    1
    2
    3
    async def async_range(start, stop):
        for i in range(start, stop):
            yield i
    
  • PEP 530 : le mot-clé async peut maintenant être utilisé dans les listes en intension (et autres compréhensions) au sein d’une coroutine.

    1
    2
    async def coroutine():
        return [i async for i in async_range(0, 10)]
    

Principales nouveautés

Interpolation de chaînes littérales — PEP 498

Loin sont maintenant les 'My name is {name}'.format(name=name) ou encore 'My name is {}'.format(name). Avec Python 3.6, le formatage de chaînes de caractères gagne en clarté avec l’interpolation littérale.

En plus du préfixe r pour définir une chaîne brute, le préfixe b pour une chaîne d’octets, arrive maintenant le préfixe f, dédié au formatage. Une chaîne préfixée de f sera interpolée à sa création, de manière à résoudre les expressions utilisées au sein de la chaîne de formatage.

La syntaxe pour l’interpolation littérale est reprise sur la syntaxe de la méthode str.format. Au détail près que sont accessibles les variables de l’espace de nom courant plutôt que seulement les valeurs qui étaient auparavant passées en arguments.

1
2
3
4
5
6
7
8
>>> name = 'John'
>>> f'Hello {name}'
'Hello John'
>>> # Équivalent à
>>> 'Hello {name}'.format(name=name)
'Hello John'
>>> 'Hello {name}'.format(**locals())
'Hello John'

Des formatages plus complets sont permis par la méthode __format__ de certains objets, comme ici pour ceux de type datetime.date.

1
2
3
4
5
6
>>> import datetime
>>> date = datetime.date.today()
>>> f'Cet article a été écrit le {date}'
'Cet article a été écrit le 2016-10-27'
>>> f'Cet article a été écrit le {date:%d %B %Y}'
'Cet article a été écrit le 27 octobre 2016'

Les accolades peuvent non seulement contenir un nom de variable, mais aussi toute expression Python valide.

1
2
3
4
5
6
>>> nb_pommes = 5
>>> print(f"J'ai {nb_pommes} pomme{'s' if nb_pommes > 1 else ''}.")
J'ai 5 pommes.
>>> nb_pommes = 1
>>> print(f"J'ai {nb_pommes} pomme{'s' if nb_pommes > 1 else ''}.")
J'ai 1 pomme.

Préservation de l’ordre des arguments nommés — PEP 468

Les paramètres du type **kwargs dans la signature d’une fonction sont maintenant assurés d’être des dictionnaires ordonnés. Les arguments nommés sont ainsi récupérés dans l’ordre où ils ont été saisis.

1
2
3
4
5
6
def xml_tag(name, **kwargs):
    lines = [f'<{name}>']
    for key, value in kwargs.items():
        lines.append(f'    <{key}>{value}</{key}>')
    lines.append(f'</{name}>')
    return '\n'.join(lines)

Cette fonction nous permet de sérialiser un extrait XML tout en conservant le bon ordre des éléments fils.

1
2
3
4
5
6
>>> print(xml_tag('book', title='Notre-Dame de Paris', author='Victor Hugo', year=1831))
<book>
    <title>Notre-Dame de Paris</title>
    <author>Victor Hugo</author>
    <year>1831</year>
</book>

Il devient aussi possible de construire facilement un objet OrderedDict.

1
2
from collections import OrderedDict
coords = OrderedDict(x=0, y=5, z=1)

Là où une liste de tuples était nécessaire dans les versions antérieures de Python.

1
coords = OrderedDict([('x', 0), ('y', 5), ('z', 1)])

On notera que cela passe, dans CPython 3.6, par une implémentation ordonnée de tous les dict.

1
2
>>> {'x': 0, 'y': 5, 'z': 1}
{'x': 0, 'y': 5, 'z': 1}

CPython reprend ici les travaux entrepris par pypy pour construire une version des dictionnaires plus compacte, c’est-à-dire occupant moins d’espace en mémoire.

Protocole de gestion des chemins de fichiers — PEP 519

Cette PEP revient sur la pathlib, bibliothèque de gestion des chemins de fichiers, pour lui ajouter son propre protocole.

Spécialisée dans la manipulation de chemins de fichiers, cette bibliothèque comporte aussi de nombreux outils pour opérer sur ces fichiers (création de fichier, de dossier, suppression, etc.)

Une nouvelle méthode spéciale est maintenant disponible pour nos objets : la méthode __fspath__. Elle ne prend aucun paramètre et doit retourner une chaîne de caractères. Tout objet implémentant cette méthode est considéré par Python comme un chemin, et peut alors être utilisé avec les fonctions Python gérant des chemins (open, os.path.join, etc.).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import os

class UserHome:
    def __init__(self, username):
        self.username = username

    def __fspath__(self):
        return f'/home/{self.username}'

for filename in os.listdir(UserHome('clem')):
    print(filename)

Nous avons ici notre propre type d’objet (UserHome), qui est interprété par les fonctions système (os.listdir dans notre cas) comme un chemin de fichier.

Préservation de l’ordre des attributs définis dans les classes — PEP 520

Similairement à la PEP 468, le dictionnaire des attributs d’une classe est maintenant assuré d’être ordonné. Il conservera alors l’ordre de définition des attributs et méthodes dans le corps de la classe.

Cela peut servir pour des classes dont l’ordre des attributs/méthodes serait important, telle qu’une énumération (Enum). Cela permet aussi une meilleure introspection des classes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class AutoEnum:
    red = None
    green = None
    blue = None

i = 0
for name, value in AutoEnum.__dict__.items():
    if not name.startswith('__') and not name.endswith('__') and value is None:
        setattr(AutoEnum, name, i)
        i += 1
1
2
>>> print(AutoEnum.__dict__)
{'__module__': '__main__', 'red': 0, 'green': 1, 'blue': 2, ...}

Il était autrefois possible d’avoir un dictionnaire ordonné pour les attributs en passant par une métaclasse. En effet, la méthode __prepare__ des métaclasses permet de spécialiser la création du dictionnaire __dict__, et donc de retourner un OrderedDict si besoin.

La volonté de cette PEP (et de la 487) est de réduire le besoin de recourir aux métaclasses pour des problèmes simples.

Simplification de la personnalisation de classes — PEP 487

Héritage

Une classe peut maintenant influer sur les classes qui en héritent. C’est ce que permet la méthode __init_subclass__ nouvellement ajoutée par cette PEP. La méthode sera appelée chaque fois qu’une classe fille sera créée (et non instanciée), et la classe fille passée en paramètre.

1
2
3
4
5
6
7
8
>>> class SubclassMePlease:
...     def __init_subclass__(cls):
...         print('Creation of', cls)
...
>>> class Ok(SubclassMePlease):
...     pass
...
Creation of <class '__main__.Ok'>

Cette méthode de classe reçoit aussi en paramètre l’ensemble des arguments nommés passés lors de l’héritage. Vous ne le saviez peut-être pas, mais des arguments peuvent être donnés lors de la création d’une classe (notamment pour la précision de la métaclasse). Il était déjà possible auparavant de les récupérer dans les méthodes __new__ et __init__ des métaclasses.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> class SubclassMePlease:
...     def __init_subclass__(cls, *, name, **kwargs):
...         print('Creation of', cls, 'with name', name)
...         cls.name = name
...
>>> class Roger(SubclassMePlease, name='roger'):
...     pass
...
Creation of <class '__main__.Roger'> with name roger
>>> Roger.name
'roger'

Descripteurs

Cette PEP ajoute aussi une nouvelle méthode spéciale aux descripteurs, la méthode __set_name__. Elle permet au descripteur de savoir quel nom d’attribut lui a été donné au sein de la classe. En effet, la méthode sera appelée suite à la création de la classe, avec comme arguments la classe et le nom du descripteur.

Cela peut s’avérer utile pour les descripteurs qui donnent accès à un autre attribut de l’objet, dont le nom peut maintenant être interpolé depuis celui du descripteur.

Dans l’exemple suivant, nous avons des descripteurs width et height qui permettent un accès en lecture à _width et _height.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class AttributeReader:
    def __set_name__(self, owner, name):
        self.attr = '_{}'.format(name)

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.attr)

class Rect:
    width = AttributeReader()
    height = AttributeReader()

    def __init__(self, width, height):
        self._width, self._height = width, height

Générateurs et compréhensions asynchrones — PEP 525 & PEP 530

Les paragraphes qui suivent demandent des connaissances sur les concepts de générateurs et de programmation asynchrone.

Les mots-clés async et await introduits avec Python 3.5 connaissent une nouvelle extension. En effet, il devient désormais possible de coupler les coroutines avec des générateurs et des compréhensions (listes en intension, generator expressions).

C’est-à-dire que l’on va pouvoir définir un générateur asynchrone, qui dépendra d’événements externes pour produire ses valeurs. L’itération sur ces générateurs devra alors être réalisée via async for plutôt qu’un simple for, au sein de coroutines.

Imaginons un générateur qui produirait les lignes d’un texte en les récupérant depuis un programme distant. Nous le représenterons ici par une itération sur un fichier, faisant une pause d’une seconde après chaque ligne pour illustrer une certaine latence.

1
2
3
4
5
6
7
import asyncio

async def produce_lines(filename):
    with open(filename, 'r') as f:
        for line in f:
            yield line.rstrip('\n')
            await asyncio.sleep(1)

On reconnaît que produce_lines est un générateur à l’utilisation du mot-clé yield en ligne 6.

Nous pouvons alors avoir une seconde coroutine, print_lines, qui itèrera sur ce générateur pour afficher les lignes produites.

1
2
3
async def print_lines(filename):
    async for line in produce_lines(filename):
        print(f'[{filename}]', line)

Et à l’utilisation :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(print_lines('corbeau.txt'))
[corbeau.txt] Maître Corbeau, sur un arbre perché,
[corbeau.txt] Tenait en son bec un fromage.
[corbeau.txt] Maître Renard, par l'odeur alléché,
[corbeau.txt] Lui tint à peu près ce langage :
[...]
>>> loop.run_until_complete(asyncio.wait([print_lines('corbeau.txt'),
...                                       print_lines('loup.txt')]))
[corbeau.txt] Maître Corbeau, sur un arbre perché,
[loup.txt] La raison du plus fort est toujours la meilleure :
[corbeau.txt] Tenait en son bec un fromage.
[loup.txt] Nous l'allons montrer tout à l'heure.
[corbeau.txt] Maître Renard, par l'odeur alléché,
[loup.txt] Un Agneau se désaltérait
[corbeau.txt] Lui tint à peu près ce langage :
[loup.txt] Dans le courant d'une onde pure.
[...]

Quant aux compréhensions asynchrones, introduites par la PEP 530, elles s’illustrent par la coroutine suivante, chargée de retourner la liste de ces lignes.

1
2
async def get_lines(filename):
    return [line async for line in produce_lines(filename)]

Notez bien le async for utilisé dans la compréhension.

L’appel à notre coroutine au sein de notre boucle événementielle nous retourne donc ici la liste des lignes.

1
2
>>> loop.run_until_complete(get_lines('corbeau.txt'))
['Maître Corbeau, sur un arbre perché,', 'Tenait en son bec un fromage.', ...]

De plus petits changements

PEP 526 : annotations de variables.

Après les paramètres de fonctions, ce sont maintenant toutes les variables qui peuvent être annotées. Le même principe est conservé : les annotations n’ont aucune utilité dans l’interpréteur Python, mais sont stockées dans l’attribut __annotations__ du scope parent (classe, module). Les annotations servent pour les outils externes d’analyse statique du code par exemple (mypy).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> a : int
>>> b : str = 'hello'
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
'hello'
>>> __annotations__
{'a': <class 'int'>, 'b': <class 'str'>}
1
2
3
4
5
6
7
>>> class A:
...     x : int = 0
...     y : int = 0
...     name : str
...
>>> A.__annotations__
{'x': <class 'int'>, 'y': <class 'int'>, 'name': <class 'str'>}

PEP 515 : underscores dans les nombres littéraux.

Les grands nombres compacts ne sont pas toujours aisés à lire. Ainsi, on mettra du temps à déceler l’ordre de grandeur dans 1000000000. Des underscores peuvent maintenant être utilisés dans l’écriture littérale des nombres, pour les séparer en différents blocs de chiffres : 1_000_000_000.

1
2
3
4
>>> 1_000_000 * 1_000
1000000000
>>> 0b_11001100_00000001
52225

  • PEP 506 : ajout d’un module secrets pour générer des nombres aléatoires plus sûrs.

  • PEP 523 : ajout d’une API pour l’évaluation de frames de CPython.

  • PEP 528 et PEP 529 : utilisation d’UTF-8 pour la console et les chemins de fichiers sur Windows.

  • PEP 509 et PEP 510 : outils d’optimisation au cœur de l’interpréteur (dictionnaires versionnés et guards sur les fonctions).

  • PEP 495 : désambiguïsation des temps identiques après changement d’heure.

  • PEP 524 : la fonction os.urandom est maintenant bloquante sur les systèmes Linux pour une sécurité accrue.

Cette version 3.6 vient aussi avec son lot de corrections de bogues, que vous pourrez retrouver dans le changelog de la version.

Ce que l'on peut attendre pour la version 3.7

Personne ne les décidant par avance, les fonctionnalités de la future version 3.7 ne sont pas encore connues. Néanmoins, ces fonctionnalités découlent des propositions faites par les utilisateurs/développeurs, généralement sur l’une de ces deux mailing lists :

  • python-dev pour les changements concernant l’intepréteur CPython ;
  • python-ideas pour les idées générales sur le langage.

Ces idées sont ensuite débattues, et mènent à une PEP si elles sont acceptées par une majorité de développeurs. La PEP sera elle-même acceptée ou refusée (par Guido, le BDFL, ou par un autre développeur si Guido est l’auteur de la PEP). En l’absence de feuilles de route définies à l’avance, les PEP peuvent arriver tard dans le cycle de développement.

Partant de ces propositions, il est possible d’établir une liste de possibles futures fonctionnalités.

Les propriétés de classes et des sous-interpréteurs, abordés dans le précédent article, ne seront pas à décrites ici.

Syntaxe pour les arguments uniquement positionnels (PEP 457)

Les arguments positionnels en Python sont les arguments passés aux fonctions qui sont assignés aux paramètres suivant leur position. Par exemple, avec une fonction def add(x, y): pass, dans l’expression add(3, 5), le paramètre x récupérera la valeur du premier argument (3), et y celle du second (5). Avec une expression telle que add(x=3, y=5) (ou add(y=5, x=3) équivalente), la correspondance se fait par le nom des paramètres, on parle alors d’arguments nommés.

Les paramètres d’une fonction en Python peuvent alors correspondre à des arguments positionnels ou des arguments nommés, il est aussi possible de faire en sorte qu’ils ne puissent être définis que via des arguments nommés (avec une fonction telle que def add(*, x, y) par exemple).

Néanmoins, bien que prévus par l’implémentation (la fonction pow ne reçoit que des arguments positionnels), il n’existe aucune syntaxe en Python permettant d’avoir des arguments uniquement positionnels. Il est ici proposé de pouvoir ajouter un caractère / lors de la définition des paramètres, lequel serait précédé des arguments uniquement positionnels.

1
2
def add(x, y, /):
    return x + y

Expressions attrapant les exceptions (PEP 463)

Cette PEP vise à permettre d’attraper des exceptions sous forme d’expressions, évitant de créer un bloc try/except dans les cas les plus simples.

L’idée serait d’avoir une expression expression except exception_type: default_value qui évaluerait l’expression expression et prendrait sa valeur, mais dans le cas où expression lèverait une exception de type exception_type, le résultat de l’expression serait default_value.

Ainsi, les deux codes suivants seraient équivalents.

1
result = a / b except ZeroDivisionError: 0
1
2
3
4
try:
    result = a / b
except ZeroDivisionError:
    result = 0

Généralisation de l’interpolation de chaînes (PEP 501)

L’interpolation de chaînes arrivée avec Python 3.6 ouvre de nouvelles perspectives pour les versions suivantes.

À l’instar du nouveau préfixe f pour les chaînes de caractères, cette PEP demande l’ajout d’un préfixe i tel que f'Bonjour {name}' soit équivalent à format(i'Bonjour {name}'). L’expression i'...' retournerait un objet d’un nouveau type (un template) dont la méthode __format__ (appelée par format(...)) se chargerait du formatage proprement dit, correspondant à l’actuel préfixe f.

Cette PEP permettrait d’appliquer plusieurs fois format sur un template et donc de le réutiliser. Il deviendrait aussi possible d’étendre le type de template pour implémenter sa propre méthode __format__ et bénéficier d’un formatage différent (pour des raisons de sécurité par exemple).

Opérateurs de coalescence (PEP 505)

Les opérateurs de coalescence (null-coalescing) sont des opérateurs qui simplifient la gestion des valeurs nulles, que l’on retrouve dans plusieurs langages (C#, Ruby, Perl, PHP, etc.). Ils permettent de n’exécuter la suite d’une expression que si la valeur utilisée n’est pas nulle.

Le concept pourrait être adapté en Python autour de la valeur None. Il est courant de rencontrer des expressions telles que obj.attr if obj is not None else 'default' pour récupérer l’attribut attr d’un objet obj incertain (pouvant être un objet du type désiré ou None). Cette expression présente le problème de s’avérer un peu lourde et d’être répétitive (obj y apparaît deux fois). Si obj était une expression plus complète, on pourrait aussi avoir un soucis de double-évaluation.

Les opérateurs de coalescence seraient au nombre de 3 :

  • ?? (None-coalescing), afin d’avoir une valeur par défaut si l’expression à gauche de l’opérateur s’évalue à None ;
  • ?. (None-aware attribute access), pour n’accéder à l’attribut d’un objet que si cet objet n’est pas None (comme dans l’exemple plus haut) ;
  • ?.[] (None-aware indexing) permet de faire de même lors de l’accès aux éléments d’un conteneur (container[...]).

On aurait alors :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> number = 5
>>> number ?? 100
5
>>> number = None
>>> number ?? 100
100
>>> number = 0
>>> number ?? 100
0

>>> obj = {'a': 5}
>>> obj?.popitem('a')
5
>>> obj = None
>>> obj?.popitem('a')
None

>>> obj = {'a': 5}
>>> obj?.['a']
5
>>> obj = None
>>> obj?.['a']
None

Python 3.6 ne bouscule donc pas l’écosystème du langage en n’apportant que quelques modifications cosmétiques. Ces modifications étaient toutefois attendues de longue date, et améliorent encore le confort des développeurs.

Cette nouvelle version est disponible sur le site officiel de Python, à la page des téléchargements.

Pour toute question/remarque sur Python 3.6 ou sur le présent article, la section commentaires est à votre libre disposition. Je me ferai un plaisir de répondre à vos interrogations !

28 commentaires

Super résumé ! Le format intro (sommaire) et développement par la suite est une bonne idée, ça permet de lire uniquement certains chapitres tout en ayant une vue d’ensemble. Petite question : Comment ça se passe en termes de calendrier pour figer un version ? Car la version précédente est sortie il y a un an et celle d’avant un an et demi ? Qui arbitre sur ce genre de question ? Qui cloture un cycle de dév pour provoquer des RC ? :)

Les realease sont gérés par un release manager et régit par une PEP qui est fixé à l’avance. Dans le cas de Python 3.6 c’est la PEP 494. C’est normalement le release manager qui décide du tempo.

Globalement le changement que tu observe ici est qu’il y a eu un changement de release manager (c’est maintenant Ned Deily, c’etait Larry Hastings avant). Il a été décidé par les dev de changer de cycle, sur proposition Ned. Tu peux lire par exemple cet article qui parle de ces discutions.

Le cycle normal d’une release python c’est :

  • 4 alphas où les fonctionalités sont ajoutés,
  • 4 betas pour stabiliser ça (a partir de la beta 1 il n’y a plus de fonctionnalités qui peuvent être rajouté).
  • Un certain nombre de release candidat où là ce sont vraiment les bugs critiques qui sont recherchés.

Le premier changement qu’il y a eu est de calendrier. Avant la release suivante débutait quand la version final était publié : Le début du dev de la 3.4 a commencé quand la 3.3 a été terminé, etc. Maintenant, dès qu’on rentre dans la phase de beta, la branche pour la version suivante s’ouvre : depuis la beta 1 de python 3.6 il y a une branche pour la 3.7 ouverte pour accueillir les nouvelles fonctionnalités. Ça se comprend puisqu’a partir de la beta on ne peut plus ajouter de fonctionnalités donc pourquoi attendre la version final pour commencer à merger les choses qui de toute façon sont pour la version suivante ? Ça raccourcis du cycle de release puisque les première alpha arrivent plus tôt !

Dans l’article que je te donne il est d’ailleurs dit :

Deily is hoping to keep the release-candidate phase quite short. The goal is to have a single release candidate that is the same as the code for the final release, but emergency fixes may lead to a second release candidate. He is planning to make the final release on December 16, which, coincidentally, is his birthday.

Il a eu le nez creux : en effet cet article est en réalité en avance : la version final a été reporté ce matin à la semaine prochaine. Et ça répond à ta dernière question : c’est le release manager qui est chargé de sortir la version et de décider si il faut une version de plus.

Pour savoir qui nomme le release manager, ça je ne suis pas sûrs, je pense que c’est la PSF qui le nomme, sur proposition des dev j’imagine.

+3 -0

Ça se comprend puisqu’a partir de la beta on ne peut plus ajouter de fonctionnalités donc pourquoi attendre la version final pour commencer à merger les choses qui de toute façon sont pour la version suivante ? Ça raccourcis du cycle de release puisque les première alpha arrivent plus tôt !

Ça alourdit aussi le travail puisque ça implique un développement actif sur deux releases simultanées. Par exemple, déterminer systématiquement dans quelle version un bug a été introduit, et merger chaque bugfix de la bêta N régulièrement dans l’alpha N+1. Selon les projets et le nombre de développeurs actifs cela peut demander une dose assez extraordinaire de discipline et de travail (souvent manuel) sur le dépôt.

+1 -0

Certes. J’imagine qu’ils vont simplement rebaser l’alpha 1 sur la release une fois publier. Il doit pas y avoir des tonnes de trucs dessus : tous les bugs fixes devant aller sur la version en cours. Mais bon on verra bien ce qu’ils décident pour la 3.7.

J’ajouterai qu’il y a effectivement eu un an et demi entre les versions 3.4.0 et 3.5.0, mais plus d’un an aussi entre 3.5.0 et 3.6.0 (un an et trois mois), il n’y a donc pas de grande différence entre ces deux durées.

Merci pour ce très bon article entwanne.

1
print(f"J'ai {nb_pommes} pomme{'s' if nb_pommes > 1 else ''}.")`

Je suis le seul a trouver que cette syntaxe, outre le fait qu’elle n’apporte pas grand chose, est un peu dégueu ?

+0 -0

Merci pour cet article. :)

Je suis le seul a trouver que cette syntaxe, outre le fait qu’elle n’apporte pas grand chose, est un peu dégueu ?

Je ne peut pas dire pour les autres, mais moi j’aime beaucoup. J’écris pas mal de script au boulot, avec des entrées-sorties en vrac, qui dépendent des paramètres. Typiquement, si j’ai p=10, e=2, je vais aller fouiller le fichier res_p10_e2 et tracer les résultats. Cette syntaxe va me simplifier la vie fortement, ça m’évitera de mettre des format pas clair et parfois lourd à écrire.

L’exemple donné est je trouve très visuel. Avec les séparateurs pour nombre numéraux, ça fait partie des petits changements qui aident à écrire des scripts lisibles sans se prendre la tête.

+1 -0

L’exemple typique où ça va aider c’est les codes où tu as des trucs dans ce genre :

1
"{foo} bar {baz} truc {bidule}".format(foo=foo, baz=baz, bidule=bidule)

Tu évite plusieurs répétions inutiles. Le risque est que des gens mettent trop de code dedans mais ça on peut pas l’empêcher. Soit on autorisait que le nom des variables, soit une expression en entier. N’autoriser qu’une partie apporterait une confusion ("On a le droit d’écrire quoi dans les f-string ?" ou "pourquoi ce bout de code marche dans les fstring et pas lui ?"). A partir du moment où il a été dit que ce serait pratique de pouvoir faire des {foo.bar} ou des {foo[bar]}, le soucis de cohérence (tout ou rien) a imposé qu’on puisse y mettre toute expression possible.

Ça va provoquer des codes dégueut. Il est possible par exemple de définir des f-strings récursivement (une f-string dans une f-string dans …) mais en soit c’est le même problème avec les lambdas : c’est censé être fait avec des expression simple. Après si des gens font n’importe quoi avec…

Perso, je trouve ces f-string plutot jolies. Meme si c’est jamais la meilleure solution, je vais la comparer a l’autre choix qu’on a (la plus courante je pense)

1
2
print("J'ai {nb_pommes} pomme{s}".format(nb_pommes=nb_pommes, s='s' if nb_pommes > 1 else '')
print(f"J'ai {nb_pommes} pomme{'s' if nb_pommes > 1 else ''}.")

Je prefere la deuxieme, je trouve ca moins verbeux.

1
"{foo} bar {baz} truc {bidule}".format(foo=foo, baz=baz, bidule=bidule)
1
"{foo} bar {baz} truc {bidule}".format(**locals())

J’utilise ca quand j’ai trop de truc a mettre en param. C’est un peu comme les f-string au final, juste qu’on pas mettre d’expression python a l’interieur.

Matt

1
2
3
4
5
"{foo} bar {baz} truc {bidule}".format(foo=foo, baz=baz, bidule=bidule)

# y'a bien plus rapide (à écrire) :

"{} bar {} truc {}".format(foo, baz, bidule)

Et c’est bien pratique pour l’utiliser plusieurs fois, par exemple pour un système de pagination :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from itertools import count
import requests

class Zds:
    domain = 'https://zestedesavoir.com'
    notif_url = '/api/notifications/?expand=1&ordering=-pubdate&page_size=20&page={}'

    def list(self):
        for i in count(1):
            url = ''.join([self.domain, self.notif_url.format(i)])
            yield from self.req.get(url).json()['results']

@tleb : mon exemple était simpliste. La version sans nom est pratique pour les exemples que tu donnes mais dans la vrai vie :

  • si il y en a plus que 3-4 ça devient galère de repérer qui va où. Explicit is better than implicit, c’est bien plus maintenable d’avoir les variables nommés dans la chaîne de formatage.
  • si une variable est utilisé plusieurs fois ça évite de la passer plusieurs fois en paramètres.

Ton exemple en secret est l’exemple typique de pourquoi la méthode format reste utile. Par contre ça ne change rien sur le fait d’utiliser un terme nommé ou anonyme. Ton exemple peut être fait avec un format nommé.

  • C’est clair que c’est plus lisible. Après, si la chaîne de formatage n’est pas stocké à part (pour bêtement insérer le contenu d’une variable dans une string), ce n’est pas vraiment nécessaire, on a la chaîne et les arguments ensembles sous les yeux.
  • Si c’est pour un petit script, on peut garder ça court et utiliser l’index du paramètre :

    1
    "{0} {1} {0}".format(foo, bar)
    

Quand il y a beaucoup d’arguments, avoir la chaîne sous les yeux ne règle pas le problème. Quand il y en a 5 ou 6, trouver auquel correspond celui en plein milieu est pas simple. Et les index présentent le même problème quand il y a pas mal d’arguments.

En soit je cherche pas a défendre les f-string. Je ne suis pas sûrs de beaucoup les utiliser. Je pense qu’elles peuvent clarifier et simplifier certains trucs. Ce qui me gène, je l’ai dit plus haut, c’est qu’on commence a avoir au moins 3 façons différentes de faire la même chose. Je pense que ce changement aurait put s’accompagner de la dépréciation de l’ancien style avec les %.

On ne peut pas déprécier de façon plus "hard" qu’actuellement (encourager à utiliser format) la syntaxe en % tant que Python 2 est encore vivant (et que des scripts peuvent tourner comme dans ansible sur du Python 2.4 ou plus). Pas sans faire gueuler un bazillion de mainteneurs d’applications dans la communauté.

PS : Il faut bien voir que ces format strings vont mettre un paquet de temps avant d’arriver dans du vrai code, d’ailleurs. Puisqu’elles ne sont compatibles qu’avec Python 3.6.

+0 -0

Oui, à voir comment évoluent les habitudes avec ces nouvelles fonctionnalités. On peut espérer une dépréciation de la syntaxe %, assez peu claire.

J’ai volontairement choisi l’exemple f"J'ai {nb_pommes} pomme{'s' if nb_pommes > 1 else ''}." pour insister sur le fait que toute expression était valide (pas juste l’accès à un attribut ou à un élément, comme c’est parfois le cas dans des moteurs de templates), et pour rappeler la syntaxe des ternaires en Python :-°

et pour rappeler la syntaxe des ternaires en Python :-°

entwanne

Je suis le seul à avoir ter qui s’autocomplète en ternary operator python dans ma barre de recherche ? :D

tleb

Moi c’est ternaire en python. ^^

+0 -0

Moi c’est rien, d’une part parce que j’utilise un moteur de recherche qui respecte ma vie privée et d’autre part parce que j’utilise howdoi pour mes recherches quand je développe.

+0 -0

Moi c’est rien, d’une part parce que j’utilise un moteur de recherche qui respecte ma vie privée et d’autre part parce que j’utilise howdoi pour mes recherches quand je développe.

Roipoussiere

C’est ma barre de recherche de Firefox qui me le propose. Ça a l’air cool howdoi, faudra que je teste.

+0 -0

J’alterne entre Google et DuckDuckGo pour le moteur de recherche, Google est quand même bien plus pertinent. Des retours sur la pertinence des résultats des autres moteurs de recherche respectueux, en particulier Quant ? Et c’est la barre de mon navigateur qui me donne la suggestion, moi aussi.

Howdoi à l’air intéressant, en effet, merci pour le lien.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte