5 fonctionnalités intéressantes en Python

Très utilisé pour le développement, le script ou bien la data science, Python offre une syntaxe simple et accessible. Le langage offre tout de même plusieurs fonctionnalités assez intéressantes. Petit tour d’horizon de cinq d’entre elles, que vous ne connaissiez peut-être pas encore.

Les f-strings

Un des points faibles du langage a toujours été le formatage de chaînes de caractères. En effet, contrairement à d’autres langages de programmation, Python n’effectue pas de conversion automatique en chaîne de caractères lors d’une concaténation. De ce fait, plusieurs façon de faire ont vu le jour au fil du temps, mais aucune n’a vraiment convaincu : syntaxe trop complexe, cast à outrance … petit tour d’horizon des différentes solutions.

La première d’entre elles, valable uniquement pour la fonction print(), consiste simplement à évaluer individuellement chaque argument transmis à la fonction. La conversion en chaîne de caractères se fait de manière automatique, et chaque expression est séparée par un espace (par défaut) :

print("Hello", True, 13, ['a', 'b', 'c'], 3.14, "world")

Une autre possibilité, assez triviale, consiste simplement à tout convertir en str dans la chaîne de caractères … pas génial comme solution. Jetons un coup d’oeil :

chaine = "Hello " + str(True) + " " + str(13) + " " + \
    str(['a', 'b', 'c']) + " " + str(3.14) + " world"

Les string templates nécessitent l’import du module string et la création d’un objet Template, pour une efficacité que je trouve plutôt … relative.

from string import Template

chaine = Template("Hello $vrai $malheur $liste $pi world").substitute(
    vrai=True, 
    malheur=13, 
    liste=['a', 'b', 'c'], 
    pi=3.14
)

Une autre technique à l’ancienne utilise le symbole % . L’avantage ici est que nous n’avons pas besoin de sortir de la chaîne de caractères et de multiplier les concaténations, mais cette écriture est totalement obsolète, et a été remplacée par la méthode format().

La méthode format() de str, existante depuis Python 2.6, a remplacé l’écriture précédente et permet de faire des choses assez sympathiques, comme formater l’écriture d’un nombre avec 3 chiffres au minimum. C’est déjà mieux, cependant pour du simple affichage de valeurs / évaluation d’expressions, ça reste assez peu esthétique.

chaine = "Hello {} {} {} {} world".format(True, 13, ['a', 'b', 'c'], 3.14)

format() reste tout de même intéressant pour certaines opérations spécifiques, telles que le formatage de date par exemple.

Bref, trop verbeux, trop illisible, trop compliqué … Heureusement la version 3.5 est venue dépoussiérer tout cela, avec les f-strings. Ici, on se rapproche un peu plus de ce que l’on connait dans d’autres langages, comme PHP par exemple : évaluer des expressions qui ne sont pas des chaînes de caractères, directement dans la chaîne de caractères.

Pour ce faire, il suffit simplement de préfixer la chaîne de caractères par la lettre f (elle doit se trouver en dehors de la chaîne de caractères), et d’écrire les expressions que l’on souhaite évaluer (variables, valeurs d’un autre type, retour de fonction, calcul, …) entre accolades.

Exemple :

pi = 3.14
chaine = f"Hello {True} {7 + 6} {['a', 'b', 'c']} {pi} world"

Facile non ?

Et si j’ai besoin d’afficher des accolades dans ma chaîne de caractères ?

Ici le caractère d’échappement (antislash) ne fonctionnera pas dans ce cas de figure. Il faudra simplement entourer les accolades … par d’autres accolades.

print(f"Ceci est un nombre entouré d'accolades : {{42}}")
# Ceci est un nombre entouré d'accolades : {42}

Par contre dans le cas où il n’y a qu’une seule accolade … là il faudra utiliser une autre solution.

Cette écriture est bien entendu valide aussi bien avec les simples, double ou triples quotes.

print(f'{2 + 2}')
print(f"{2 + 2}")
print(f"""{2 + 2}""")

La décomposition

La décomposition (appelée également déstructuration dans d’autres langages, unpacking en anglais) consiste simplement à affecter les objets contenus dans un tuple à plusieurs variables.

# Les parenthèses sont facultatives
a, b, c = 1, 2, 3

print(a)
# 1
print(b)
# 2
print(c)
# 3

Bien entendu, le nombre de variables à affecter doit strictement correspondre au nombre d’éléments présents dans le tuple.

Toutes les variables sont affectées « en même temps » : il n’y a pas d’ordre de priorité. L’exemple suivant va nous permettre de mieux comprendre cette affirmation.

Un des exercices d’algorithmique les plus basiques, celui par lequel nous sommes tous passés lorsque l’on a commencé la programmation, consiste à inverser la valeur de deux variables. Voici sans doute comment vous avez appris à faire :

a = 5
b = 8
tmp = b
b = a
a = tmp

Voici comment on fait en Python :

a = 5
b = 8
a, b = b, a

Incroyable non ?

La composition permet notamment de mettre en place des « retours multiples » pour les fonctions. En réalité, ce n’est qu’un tuple qui est retourné, et récupéré dans deux variables différentes (ou plus) grâce à cette notion de composition.

def diviser(a, b):
    return a / b, a // b, a % b


decimale, euclidienne, reste = diviser(7, 5)

print(decimale)
# 1.4
print(euclidienne)
# 1
print(reste)
# 2

C’est valable pour les list également.

ma_liste_a_decomposer = ['alpha', 'beta', 'gamma']
a, b, c = ma_liste_a_decomposer

On peut aussi faire ce genre de choses …

a, b, *c = "Un", "jour", "je", "serai", "le", "meilleur", "dresseur"

print(a)
# Un
print(b)
# jour
print(c)
# ['je', 'serai', 'le', 'meilleur', 'dresseur']

Créer une séquence avec plusieurs fois le même élément

Lorsqu’on souhaite créer une list avec n fois le même élément, il y a plusieurs manières de faire. La mauvaise d’abord :

ma_liste = []

for _ in range(10):
    ma_liste.append('hey')

print(ma_liste)
# ['hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey']

Et ensuite la bonne manière de faire :

ma_liste = ['hey'] * 10

print(ma_liste)
# ['hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey', 'hey']

Ce qui est intéressant, outre le fait que cette deuxième écriture soit plus courte aussi bien en terme de syntaxe qu’en terme de temps d’exécution, c’est qu’elle est compatible avec les tuple également, et même avec les chaînes de caractères !

Comme dans la section précédente, reprenons un classique des exercices de bases en algorithmique : créer un carré de n étoiles. Habituellement, on utiliserait un système de boucles imbriquées, l’une pour créer les lignes d’étoiles, l’autre pour répéter ces lignes d’étoiles.

Ici, une seule boucle suffit :

n = int(input("Quelle est la taille du carré ? > ")

for _ in range(n):
    print('*' * n)

Les listes en compréhension

Il arrive souvent que l’on souhaite créer une liste à partir d’une autre liste. Par exemple, à partir d’une list de int, créer une list contenant uniquement les positifs. Dans ce cas, on sera amené à écrire ce genre de chose :

nombres = [4, 9, -3, 1, 0, 3, 9, 12, -10, 0, -4, 7]
positifs = []
for n in nombres:
    if n > 0:
        positifs.append(n)

Ici, nous avons dans un premier temps déclaré une list vide (positifs = []), puis effectué une boucle for sur cette list (for n in nombres), vérifié si chacun des nombres était positif (if n > 0), et enfin si tel était le cas, ajouté ce nombre à la liste (positifs.append(n)). Du classique niveau algorithmique.

Saviez-vous qu’il existait une autre manière de faire, beaucoup plus courte et performante ? Il s’agit des listes en compréhension (également appelées liste en intension). Voici un exemple qui reproduit la même chose mais avec cette syntaxe particulière :

nombres = [4, 9, -3, 1, 0, 3, 9, 12, -10, 0, -4, 7]
positifs = [n for n in nombres if n > 0]

Nous sommes passés de quatre lignes … à une seule.

Alors effectivement, pour un novice, c’est un peu déroutant. Ce qu’il faut comprendre, c’est que le résultat sera le même, mais la mécanique différente. Ici, pas d’initialisation de list vide et de remplissage au fur et à mesure : la list est créée dynamiquement en fonction de la boucle et la condition insérée à l’intérieur des crochets.

Analysons un peu plus en détail cette instruction : ici, on demande littéralement « je souhaite créer une list nommée positifs, où les valeurs de la liste correspondent à n pour tout n se trouvant dans la liste nombres si n est supérieur à 0 ». Et c’est tout !

Dans cet exemple, nous nous sommes contentés d’ajouter des valeurs déjà existantes, en fonction d’un filtre, qui correspond à la condition (if n > 0).

Il est également possible d’appliquer une transformation sur les valeurs à ajouter à la liste. Par exemple, je souhaite mettre au carré toutes les valeurs d’une list de int et les convertir en str :

nombres = [4, 9, -3, 1, 0, 3, 9, 12, -10, 0, -4, 7]
carres_en_str = [str(n ** 2) for n in nombres]

print(carres_en_str)
# ['16', '81', '9', '1', '0', '9', '81', '144', '100', '0', '16', '49']

La syntaxe de la liste en compréhension est généralement toujours la suivante (la condition étant facultative) :

liste_en_comprehension = [<valeur a ajouter> : <boucle de parcours> : <condition>]

Pour ceux qui utilisent filter() et map(), vous devriez vous intéresser de près à cette écriture. Dans la plupart des cas, elle permet de faire exactement la même chose que ces deux fonctions, et de manière plus simple qui plus est.

Peut-on créer d’autres structures de données avec cette écriture ?

Oui. On peut créer des set en compréhension, des dict en compréhension (dans ce cas, il faudra s’assurer de bien renseigner la clé et la valeur).

lettres = ('alpha', 'beta', 'gamma', 'delta', 'epsilon')
dict_comprehension = {lettre: index for index, lettre in enumerate(lettres, start=1)}

print(dict_comprehension)
# {'alpha': 1, 'beta': 2, 'gamma': 3, 'delta': 4, 'epsilon': 5}

Pour les tuple, c’est un peu particulier, puisque ce qui est généré est un … générateur. On y consacrera un autre article.

Les *args et **kwargs

On termine avec une fonctionnalité relative aux … fonctions.

Il est possible en Python d’avoir un paramètre de fonction qui attend un nombre indéfini d’arguments (y compris aucun). Nous allons l’appeler « paramètre de taille variable ». Ce paramètre de taille variable doit respecter quelques critères :

  • il doit être préfixé par un astérisque (*) dans la déclaration de la fonction ;
  • il doit également se trouver après les paramètres positionnels ;
  • et enfin, il ne peut y avoir qu’un seul paramètre de taille variable par fonction.

Dans le corps de la fonction, le paramètre de taille variable (sans l’astérisque) sera récupéré sous la forme d’un tuple.

Voici un petit exemple très simple :

def calculer_moyenne(*notes):
    if len(notes) == 0:
        return 0
    return sum(notes) / len(notes)

Cette fonction prend un nombre variable de notes et retourne la moyenne (la somme divisée par le nombre d’éléments). La condition à la ligne 2 permet d’éviter de lever une exception car Python n’aime pas trop les divisions par 0.

Ainsi je peux appeler la fonction comme cela :

print(calculer_moyenne(16, 14))
print(calculer_moyenne(16, 14, 8, 12))
print(calculer_moyenne(16, 14, 8, 12, 11, 3, 17, 10, 9.5, 13, 15, 16.5))
print(calculer_moyenne())

À noter : si aucun argument n’a été transmis au paramètre de taille variable, il n’est pas None, mais correspond bien à un tuple vide.

Autre exemple avec une fonction qui effectue une simple addition. Nous avons besoin d’au moins deux nombres pour faire le calcul.

def additionner(x, y, *nombres):
    return x + y + sum(nombres)

print(additionner(2, 3))
print(additionner(2, 3, 9, 3, 7, 5, 3, 4))

Et que se passe-t-il dans le cas où nous avons des paramètres avec une valeur par défaut ?

On peut l’écrire comme ceci :

def ma_fct_1(a, b, c=99, *d):
    print('a =', a)
    print('b =', b)
    print('c =', c)
    print('d =', d)

Ou comme cela :

def ma_fct_2(a, b, *d, c=99):
    print('a =', a)
    print('b =', b)
    print('c =', c)
    print('d =', d)

La différence concerne la valeur du paramètre c : dans le premier cas, si l’appel à la fonction se fait avec au moins 3 arguments, la valeur de c sera systématiquement écrasée par la valeur du troisième argument, ; tandis que dans le deuxième cas, il faudra l’indiquer explicitement à l’aide d’une étiquette.

ma_fct_1(1, 2)
# c = 99
# d = ()

ma_fct_1(1, 2, 3, 4, 5, 6)
# c = 3
# d = (4, 5, 6)

ma_fct_2(1, 2, 3, 4, 5, 6)
# c = 99
# d = (3, 4, 5, 6)

ma_fct_2(1, 2, 3, 4, 5, 6, c=7)
# c = 7
# d = (3, 4, 5, 6)

Au fait, connaissez-vous une fonction native qui utilise des *args ? Vous avez 5 secondes pour y réfléchir … Eh oui, c’est la fonction print() ! D’ailleurs cette fonction attend également qui possèdent une valeur par défaut : sepend et file.

Dans les exemples précédents, les valeurs du paramètre de taille variable n’avaient pas véritablement de valeur sémantique. La seule manière d’identifier un élément dans le tuple était son indice.

Grâce aux « paramètres de taille variable nommés » (que l’on va appeler kwargs pour économiser quelques caractères), nous avons la possibilité d’appeler la fonction avec n’importe quel argument en le faisant correspondre à n’importe quel paramètre, grâce aux étiquettes.

Voici les critères que doit respecter le kwargs :

  • il doit être préfixé par un double astérisque (**) dans la déclaration de la fonction ;
  • il doit se trouver après les paramètres positionnels, les paramètres avec valeur par défaut et les paramètres de taille variable (à la fin quoi) ;
  • il ne peut y avoir qu’un seul paramètre kwargs par fonction ;
  • on peut avoir un *args et un **kwargs dans une fonction.

Dans la fonction, le paramètre kwargs sera récupéré sous la forme d’un dict, la clé correspondant au nom du paramètre indiqué (converti en str), et la valeur correspondant à la valeur de l’argument.

Un exemple tout simple, qui combine le tout :

def ma_fct_3(a, b, c=99, *d, **e):
    print('a =', a)
    print('b =', b)
    print('c =', c)
    print('d =', d)
    print('e =', e)

ma_fct_3(1, 2, 3, 4, 5, 6, 7, 8, toto=11, tata=12, titi=13)
# a = 1
# b = 2
# c = 3
# d = (4, 5, 6, 7, 8)
# e = {'toto': 11, 'tata': 12, 'titi': 13}

Et voilà, nous avons fait le tour de quelques fonctionnalités assez sympa en Python. Dans des articles suivants, nous détaillerons des fonctionnalités un peu plus avancées du langage.

Laquelle de ces fonctionnalités vous semble la plus intéressante ?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *