Up.oN
#24/03/2025 Update: #02/04/2025

Les fondamentaux

La syntaxe

Séparer les instructions

Python ne se sert pas de points-virgules pour délimiter les instructions. À la place, l'indentation et les espaces       structurent le code.

if instruction:
    instruction

Lorsqu'une instruction est trop longue, Python permet de la répartir sur plusieurs lignes en utilisant le caractère de continuation \.

Le commentaire

Les commentaires sur une ligne, commencent par le symbole #.

Les blocs de commentaires s'effectuent via le symbole """ ou '''.

# Ceci est un commentaire sur une seule ligne
x = 5  # Ce commentaire explique que x est assigné à 5
x = 5  """ Ce commentaire explique que x est assigné à 5 """

"""
Ceci est un commentaire sur plusieurs lignes
utilisant une chaîne de caractères multilignes.
Il est souvent utilisé pour des blocs de texte plus longs.
"""

# DOCSTRINGS
def addition(a, b):
  """
  Cette fonction prend deux nombres et retourne leur somme.

  :param a: Le premier nombre
  :param b: Le deuxième nombre
  :return: La somme de a et b
  """
    return a + b

Les identifiants et les mots-clés

Pour déterminer le nom des variables, fonctions, classe, modules, on va les écrire en camel_case. Python est sensible à la casse.

Pour avoir la liste complète des mots clés:

import keyword
print(keyword.kwlist) 

L"esprit "pythonic"

L'esprit "pythonic" :

  • Lisibilité: Le code est facile à lire et à comprendre.
  • Expressivité: Chaque ligne de code traduit clairement l'intention du développeur.
  • Efficacité: Des solutions concises permettent souvent d'éviter le "surcodage" et de réduire les risques d’erreur.
Deux techniques illustrent parfaitement cet esprit: le slicing et l'Unpacking.

Slicing

Le Slicing permet d'extraire facilement une sous-séquence d'une liste, d'un tuple ou d'une chaîne de caractères grâce à une syntaxe intuitive (par exemple, liste[1:4] pour obtenir les éléments d'index 1 à 3). Cela favorise une écriture de code à la fois concise et expressive, permettant de manipuler les séquences sans boucles complexes.

Unpacking

L'unpacking consiste à décomposer une séquence ou un itérable en plusieurs variables en une seule ligne (comme: a, b, c = [1, 2, 3]). Cette fonctionnalité offre une approche élégante pour assigner des valeurs à plusieurs variables, tout en permettant des extractions partielles grâce à l'opérateur * (par exemple:, a, *middle, c = [1, 2, 3, 4, 5]).

Les variables

Former une chaîne de caractères

Pour former une chaîne de caractère on peut utiliser l'apostrophe (') ou les guillemets (").
On peut également utiliser des 'triple' apostrophes: ''', ce qui permet d'écrire une chaîne de caractère sur plusieurs lignes:

ma_super_chaine = '''
Bienvenue 
Tout le monde ! 
\o/ 
'''

Le caractère d'échappement est: \.
Si on ne souhaite pas qu'il soit interpréter on doit ajouter la lettre 'r' devant la chaîne de caractère:

r'C:\tmp\mon\repertoire'

Pour ajouter un variable à une chaîne de caractère, on utilise la lettre 'f' et les accolades {}:

name = 'John'
msg = f"Bonjour {name}"
name = 'John'
msg = f"Bonjour {name}"

Pour concaténer on va utiliser le caractère +.
A savoir, on peut concaténer deux chaînes de caractères entre elles en les séparant par un espace:

msg = 'Bonjour ' 'le monde!'

En Python une chaîne vide est évalué à False.

Jouer avec les strings

Dans cette section nous alors voir les opérations possibles sur une chaîne de caractères.

Accéder à un caractère
msg = 'Hello World'

print(str[0]) # H 
print(str[1]) # e
            
print(str[-1]) # d 
print(str[-2]) # l 
Longueur d'une chaîne

Utiliser la fonction len()

msg = 'Hello World'
msg_len = len(msg)
print(msg_len) # 11

Le slicing

Quelques chose de très sexy ici pour extraire une portion d'une chaîne, le slicing.
string[start:end].
Ici, start et end ne sont pas obligatoire. Ainsi, on peut retirer une portion à gauche ou à droite de la chaîne.

text = "Bonjour le monde"

# Extrait les caractères de l'indice 3 à 9
print(text[3:9])  # Sortie : "jour l"
            
# Extrait les caractères de l'indice 0 à 7 avec un pas de 2
print(text[0:7:2])  # Sortie : "Bnj"
            
# Inverse la chaîne
print(text[::-1])  # Sortie : "ednom el ruojnoB"            

Remarque: le slicing fonctionne aussi sur les listes, les tuples et autres objets supportant l'indexation. Un vrai délice!

Les chaînes de caractères sont immuables
Si vous souhaitez modifier une chaîne de caractères, il faut en créer une nouvelle.

map()

Il s'agit ici de notions plus "avancée", je conseil de suivre le cour L'Algorithmique si vous n'êtes pas à l'aise avec les boucles et les fonctions anonymes.
Je considère qu'il est important de connaître les différentes fonctionnalités offertes par le langage concernant les chaînes de caractères. Vous pouvez passer cette partie pour l'instant et y revenir ultérieurement si besoin.

La fonction map() est généralement utilisée avec des itérables comme des listes ou des tuples. Une chaîne de caractères (str) est aussi un itérable en Python (chaque caractère peut être parcouru).

Ci-après quelques exemples d'utilisation de map() sur une chaîne de caractère.

Transformer chaque caractère en majuscule:

text = "Bonjour le monde"

# Utilisation de map() pour convertir chaque caractère en majuscule
resultat = map(str.upper, text)

# Conversion en liste puis en chaîne de caractères
print("".join(resultat))  # Affiche : "BONJOUR LE MONDE"

Explication:

  • str.upper est appliqué à chaque caractère.
  • L'itérateur map est transformé en une liste de caractères, puis joint avec "".join() pour reformer une chaîne.

Remplacer les espaces par un tiret (-):

text = "Bonjour le monde"

# Remplacer chaque espace par un tiret
resultat = map(lambda c: "-" if c == " " else c, text)

print("".join(resultat))  # Affiche : "Bonjour-le-monde"

Explication : La fonction lambda remplace les espaces par -, sinon elle garde le caractère d'origine.

Obtenir les codes ASCII de chaque caractère:

text = "Bonjour"

# Utilisation de map() pour obtenir le code ASCII de chaque caractère
resultat = map(ord, text)

print(list(resultat))  
# Affiche : [66, 111, 110, 106, 111, 117, 114]  (Codes ASCII des lettres de "Bonjour")

Explication : ord(c) renvoie le code ASCII du caractère c.

filter()

Il s'agit ici de notions plus "avancée", je conseil de suivre le cour L'Algorithmique si vous n'êtes pas à l'aise avec les boucles et les fonctions anonymes.
Je considère qu'il est important de connaître les différentes fonctionnalités offertes par le langage concernant les chaînes de caractères. Vous pouvez passer cette partie pour l'instant et y revenir ultérieurement si besoin.

filter() permet de filtrer un itérable en ne retenant que les éléments qui satisfont une condition.
Voici un exemple avec une chaîne de caractères, extraire uniquement les lettres majuscules:

texte = "Hello World! Bienvenue sur Python."
# La fonction lambda vérifie si le caractère est une majuscule
majuscules = filter(lambda c: c.isupper(), texte)

# Reconstituer la chaîne filtrée
resultat = "".join(majuscules)
print(resultat)  # Affiche : "HWBP"

Explication: La méthode isupper() renvoie True pour les lettres majuscules. Ici, filter() parcourt la chaîne caractère par caractère.

reduce()

Il s'agit ici de notions plus "avancée", je conseil de suivre le cour L'Algorithmique si vous n'êtes pas à l'aise avec les boucles et les fonctions anonymes. Notamment la section à propos de reduce().
Je considère qu'il est important de connaître les différentes fonctionnalités offertes par le langage concernant les chaînes de caractères. Vous pouvez passer cette partie pour l'instant et y revenir ultérieurement si besoin.

reduce() peut-être utilisé sur une chaîne de caractères. Le principe: on indique une fonction qui est d'abord appliquée aux deux premiers éléments de l'itérable, puis le résultat est combiné avec le troisième élément, et ainsi de suite jusqu'à obtenir une valeur finale.

# Concaténation de chaînes de caractères
from functools import reduce

mots = ["Bonjour", "le", "monde"]
phrase = reduce(lambda x, y: x + " " + y, mots)
print(phrase)  # Affiche : "Bonjour le monde"

Ici, la fonction lambda concatène deux chaînes en insérant un espace entre elles.

sorted()

Il s'agit ici de notions plus "avancée", je conseil de suivre le cour L'Algorithmique si vous n'êtes pas à l'aise avec les boucles.
Je considère qu'il est important de connaître les différentes fonctionnalités offertes par le langage concernant les chaînes de caractères. Vous pouvez passer cette partie pour l'instant et y revenir ultérieurement si besoin.

Trier une chaîne via sorted()

chaine = "python"
resultat = sorted(chaine)
print(resultat)  # Affiche : ['h', 'n', 'o', 'p', 't', 'y']

# Pour reconstituer une chaîne triée :
chaine_triee = "".join(sorted(chaine))
print(chaine_triee)  # Affiche : "hnopty"

Les nombres

Pour les fondamentaux, on ne parle ici que des int et float.

Rien de particulier ici, le float est dominant.

Python supporte les underscores dans les nombres:

count = 10_000_000_000
print(count) # 10000000000

Les booléens

Rien de particulier, il faut respecter la casse ainsi on assigne un booléen avec le valeur suivante: True ou False.

Si on souhaites savoir si une valeurs est vraie ou fausse, on dispose de la fonction bool().

Les constante

Ce n'est pas supporté dans Python, la convention est de créer une variable et de l'écrire en majuscule pour indiquer qu'il s'agit d'une constante:

FILE_SIZE_LIMIT = 2000

Le "Transtypage"

Python est comme le Php faiblement typé, mais typé tout de même.
Ainsi, nous disposons de fonctions de "transtypage": int(str), float(str), bool(val), str(val).

Pour avoir le type d'une variable, on dispose de la fonction type().

Type Hints

Introduction

Remarque personnel:
Priorité à la compréhension du langage: Avant de se plonger dans les annotations de type, il est essentiel de bien comprendre les bases de Python : syntaxe, structures de contrôle, fonctions, classes, etc. Les annotations de type sont un outil supplémentaire qui peut être introduit une fois que vous êtes à l'aise avec le langage.
Venant de php, j'ai cherché à "typer" assez rapidement, c'est pourquoi je place cette section ici.

Python, un langage dynamique et fortement typé

Python est réputé pour sa syntaxe claire et son approche dynamique. Cela signifie que les variables ne sont pas déclarées avec un type fixe; ce qui importe, c'est le comportement des objets à l'exécution (principe du duck typing). Cependant, Python reste fortement typé : il interdit par exemple l'addition d'un nombre et d'une chaîne de caractères sans conversion explicite.

Pourquoi utiliser des Type Hints ?

Les annotations de type (Type Hints) permettent de:

  • Améliorer la lisibilité du code en documentant explicitement ce que chaque fonction attend et renvoie.
  • Faciliter la maintenance, surtout dans les gros projets ou lorsqu'on travaille en équipe.
  • Permettre une vérification statique (avec mypy, pyright, etc.) qui aide à détecter avant l'exécution des incohérences ou erreurs de type.
  • Optimiser l'auto-complétion et la navigation dans les IDE modernes tels que VS Code ou PyCharm.

Concept de typage graduel

Le système de typage en Python est graduel : vous pouvez ajouter des annotations à mesure que votre projet évolue, sans devoir les imposer partout ni modifier le comportement du runtime. Vous bénéficiez ainsi d'un compromis entre souplesse et sécurité.

Syntaxe de Base des Type Hints

Annotation des variables

L'annotation se fait en plaçant un deux-points après le nom de la variable suivi de son type (et éventuellement une valeur) :

# Exemple simple
nom_programme: str = "Python"
version: float = 3.10
est_actif: bool = True

Même si ces annotations servent de documentation et d'aide statique, Python ne les impose pas à l'exécution. Vous pouvez par exemple écrire :

texte: str = "Bonjour"
texte = 42  # Python acceptera ceci, mais un outil statique comme mypy le signalera.
print(texte)

Annotation des fonctions

Des paramètres et d'un retour

Pour annoter une fonction, on précise après le nom de chaque paramètre son type et, après le symbole ->, le type de retour:

def saluer(nom: str) -> str:
    return f"Bonjour {nom} !"
          
# Exemple d'utilisation :
message = saluer("Alice")
print(message)  # Affiche: Bonjour Alice !
Fonctions sans retour significatif

Si la fonction ne renvoie rien, le type de retour est None:

def afficher(message: str) -> None:
    print(message)

Types Complexes et Avancés

Les structures de données sont étudiées un peu plus loin, voici ici comment appliquer le Type Hints sur ces derniers.

Collections

Les Type Hints permettent de décrire le contenu des collections :

# Liste d'entiers
nombres: list[int] = [1, 2, 3, 4]

# Dictionnaire avec des chaînes comme clés et des flottants comme valeurs
poids: dict[str, float] = {"pomme": 0.35, "banane": 0.22}

# Tuple fixe (int, str, float)
personne: tuple[int, str, float] = (1, "Alice", 3.14)

Types Unions et Optionnels

Union: permet de dire qu'une variable peut être de plusieurs types, via l'opérateur |.

def traiter(valeur: int | str) -> None:
    print(valeur)

Optional: c'est en fait une Union avec None: à X | None).

def trouver_utilisateur(id: int) -> str | None:
# Retourne une chaîne si l'utilisateur est trouvé, sinon None
    ...

Génériques avec TypeVar et Generic

Les types génériques permettent d'écrire des fonctions ou classes qui fonctionnent avec plusieurs types tout en gardant la cohérence des annotations:

from typing import TypeVar, Generic

T = TypeVar('T')

def premier_element(liste: list[T]) -> T:
    return liste[0]

# Exemple d'utilisation :
print(premier_element([1, 2, 3]))    # Affiche 1
print(premier_element(["a", "b", "c"]))  # Affiche "a"

Explications:

  • TypeVar est utilisé pour créer une variable de type. ici, T = TypeVar('T') crée une variable de type nommée T.
  • Le 'T' passé en paramètre à TypeVar est simplement une chaîne de caractères qui sert de nom pour cette variable de type. Vous pouvez utiliser n'importe quel nom, mais T est une convention courante pour indiquer un type générique.
  • T peut être remplacé par n'importe quel type concret lorsque la fonction est appelée. Cela signifie que T peut être un int, un str, une list, ou tout autre type.

Literal Types

Les Literal Types (introduits par la PEP 586) vous permettent de restreindre la valeur d'une variable à un ensemble précis de valeurs littérales. Par exemple, vous pouvez indiquer qu'une fonction n'accepte qu'une chaîne de caractères ayant exactement une valeur déterminée, ce qui renforce la précision de vos annotations. Un exemple simple:

from typing import Literal

def set_mode(mode: Literal['rouge', 'vert', 'bleu']) -> None:
    print(f"Mode défini sur {mode}")

set_mode('rouge')  # OK
# set_mode('jaune')  # Ceci sera signalé par un vérificateur statique comme mypy.

Ce type d'annotation est très utile pour décrire des paramètres qui ne doivent prendre qu'un ensemble limité de valeurs prédéfinies.

Le mot-clé final

Introduit dans la PEP 591, le mot-clé final permet d'indiquer qu'une variable, une méthode ou une classe ne doit pas être modifiée ou redéfinie par la suite. Cela sert principalement de documentation pour les développeurs et d'aide aux outils de vérification statique, puisqu'à l'exécution, Python n'impose pas ces contraintes. Par exemple:

from typing import final

@final
class Base:
    def méthode(self) -> None:
        print("Méthode dans Base")

# Toute tentative de sous-classement de Base sera signalée par un vérificateur de types.
class Fille(Base):  # Erreur détectée par mypy ou pyright
    def méthode(self) -> None:
        print("Redéfinition dans Fille")

Ici, l'utilisation de final montre clairement que la classe Base (ou une méthode décorée avec final) n'est pas destinée à être étendue ou redéfinie.

NewType pour distinguer des types apparentés

Lorsqu'une valeur de base doit être considérée comme un type distinct (par exemple pour éviter les confusions entre identifiants), NewType permet de créer un alias fortement typé:

from typing import NewType

UserId = NewType('UserId', int)
          
def get_user_name(user_id: UserId) -> str:
# Traitement pour récupérer le nom d'un utilisateur
    ...
          
user = get_user_name(UserId(1001))

Annotation dans les Classes

La POO sera abordée plus loin, mais voici comment utiliser le Type Hints avec les classes.

Les Type Hints s'appliquent également aux attributs et méthodes des classes. Elles aident à documenter l'interface d'un objet.

class Personne:
    nom: str  # Annotation de l'attribut

    def __init__(self, nom: str) -> None:
        self.nom = nom

    def saluer(self) -> str:
        return f"Bonjour, {self.nom} !"

# Utilisation :
p = Personne("Alice")
print(p.saluer())  # Affiche: Bonjour, Alice !

Pour des classes de données, vous pouvez également utiliser le décorateur @dataclass qui simplifie l'écriture en générant automatiquement l'init, le repr, etc., tout en permettant les annotations de type.

Avantages des Type Hints

Les annotations de type présentent de nombreux bénéfices, notamment:

  • Lisibilité accrue: elles servent de documentation intégrée qui facilite la compréhension du code.
  • Détection précoce d'erreurs: grâce aux outils de vérification statique (comme mypy ou pyright), on peut intercepter des incohérences de types avant même l'exécution.
  • Amélioration de l'auto-complétion: les IDE et éditeurs modernes peuvent utiliser ces informations pour offrir une auto-complétion plus précise.
  • Meilleure maintenance: dans les projets volumineux ou en équipe, les Type Hints aident à comprendre l'interface des fonctions et des classes, ce qui facilite la mise à jour et la refactorisation du code.

Outils de Vérification Statique

Mypy

Mypy est un outil de vérification statique qui lit les annotations de type et signale les incohérences.
Il est nécessaire de l'installer: pip install mypy

Il s'utilise via le mot clé mypy: mypy mon_script.py

Si vous avez une fonction annotée mais qu'un type incorrect est passé, mypy vous l'indiquera avant l'exécution

Pyright

Pyright est une "alternative" très rapide, souvent intégrée dans des éditeurs comme VS Code, qui réalise lui-même une vérification statique des types.

Bonnes Pratiques et Conseils

Typage graduel

Il est recommandé d'ajouter des annotations progressivement, en commençant par les parties critiques du code, par exemple les interfaces publiques et les fonctions clés.

Concentrez le typage sur les frontières

Pour les librairies ou les modules à usage externe, spécifiez clairement le contrat de vos fonctions (paramètres et retours) pour aider les utilisateurs.

Évitez d'abuser du type Any

L'usage abusif de Any réduit les avantages du typage statique; mieux vaut être précis pour capter d'éventuelles erreurs.

Protocoles et typage structurel

La notion de Protocol (introduite dans la PEP 544) permet de définir des contrats de type basés sur le comportement (duck typing). Cela est particulièrement utile lorsque vous souhaitez définir des interfaces « non contraignantes » sans forcer une hiérarchie d'héritage.

from typing import Protocol

class Duck(Protocol):
    def quack(self) -> str: ...
    def nager(self) -> None: ...

class VraieCanard:
    def quack(self) -> str:
        return "Quack!"
    def nager(self) -> None:
        print("Je nage!")

def faire_nager(oiseau: Duck) -> None:
    oiseau.nager()

faire_nager(VraieCanard())  # Valide même si VraieCanard n'hérite pas explicitement de Duck

Pour que isinstance puisse fonctionner avec un Protocol, pensez à utiliser le décorateur @runtime_checkable si nécessaire.

Limitations

Bien que très utile, le système de Type Hints présente quelques limites :

  • Pas de vérification à l'exécution: les annotations ne sont pas contraintes par l'interpréteur Python, elles servent uniquement aux outils externes.
  • Complexité potentielle: pour de très petites applications, ajouter trop d'annotations peut alourdir le code.
  • Cas particuliers: certains scénarios dynamiques restent difficiles à typer strictement (e.g. utilisation intensive de la réflexion ou de code généré dynamiquement).


Remarque: il existe d'autres types de variables comme les structures de données, elles sont passées en revue dans un autre chapitre.