Python : les bases

nora.nckm.eu

Illustration

Cette page présente les bases du langage Python. Pour les notions avancées se référer au pages suivantes : l'orienté objet, les modules et l'environnement.

La documentation officielle : docs.python.org

Types natifs

Python est un langage dynamique et fortement typé. Le type d'une variable est défini lorsqu'une valeur lui est affectée et ce type peut changer à tout moment. Pour réaliser certaines opérations, il est nécessaire de convertir explicitement une variable d'un type à un autre.

Chaînes de caractères

Les chaînes de caractères : str.

chaine_guillemets_simples = 'Lorem ipsum'
chaine_guillemets_doubles = "Lorem ipsum"
chaine_echappement = "L'orem ipsum"
chaine_echappement = 'L\'orem ipsum'

chaine_multiligne = """
Lorem ipsum
dolor sit amet,
consectetur adipiscing elit.
"""

chaine_caractere_unicode = "\u2764"

chaine_brute = r"c:\toto\titi"

Nombres

Les nombres entiers : int, les nombres décimaux : float.

1000000 # nombre entier
1_000_000 # avec séparateur
10.0 # nombre décimal

round(2.2) # 2
round(2.8) # 3
round(2.333, 1) # 2.3

Booléens

Les booléens : bool.

True
False

issubclass(bool, int) # True

bool("") # False
bool(0) # False
bool(0.0) # False
bool([]) # False
bool(()) # False
bool({}) # False

Constructeurs

Ils peuvent être utilisés pour convertir des objets d'un type à l'autre.

str(10) # '10'
int("10") # 10
float(10) # 10.0
bool(10) # True

Variables

Règles de nommage

Les conventions de codage sont détaillées dans le document PEP 8, en résumé :

Affectation

# Affectation simple
a = 10
b = 20

# Affectation parallèle
a, b = 10, 20

# Affectation multiple
a = b = 10

Mémoire

Python supprime automatiquement les objets qui ne sont plus associés à une variable pour libérer de la place en mémoire (mécanisme de garbage collector).

Cependant, pour des raisons d'optimisation, certains objets sont créés au démarrage de l'interpréteur et garde leur place en mémoire (mécanisme de small integer caching).

# Adresse d'un objet en mémoire
id(a) # 4514223600

# Objets comme None, True ou False
id(True) # 4304888192
id(True) # 4304888192

# Nombres entre -5 et 256 inclus
id(5) # 4305279584
id(5) # 4305279584

# Chaînes de moins de 10 caractères
id("bonjour") # 4305279584
id("bonjour") # 4305279584

Lorsqu'on compare des objets, il faut distinguer l'égalité et l'identité. Deux objets sont égaux si leurs valeurs sont égales alors que deux objets sont identiques si leurs emplacements mémoire sont identiques.

a = [1, 2, 3]
b = [1, 2, 3]
a == b # True
a is b # False

a = 1000
b = 1000
a == b # True
a is b # False

a = 256
b = 256
a == b # True
a is b # True

Formatage de chaîne de caractères

Il existe plusieurs méthodes pour formater une chaîne de caractère : la concaténation, la méthode format et les f-string.

# Concaténation
nom = "Python"
age = 30
phrase = "Je m'appelle " + nom + " et j'ai " + str(age) + " ans."

# Méthode format
phrase = "Je m'appelle {} et j'ai {} ans.".format(nom, age)
phrase = "Je m'appelle {0} et j'ai {1} ans.".format(nom, age)
phrase = "Je m'appelle {nom} et j'ai {age} ans.".format(nom=nom, age=age)

# F-string
phrase = f"Je m'appelle {nom} et j'ai {age} ans."

langage = {"Python": 30}
phrase = f"Python a {langage['Python']} ans."
phrase = f'Python a {langage["Python"]} ans.'

On peut appliquer différentes options de formatage qui sont détaillées sur pyformat.info.

nombre = 12
f"{nombre:04d}" # 0012

date = datetime(2022, 4, 1, 22, 0)
f'{date:Le %Y-%m-%d à %H:%M}' # Le 2022-04-01 à 22:00

phrase = "Lorem ipsum dolor sit amet"
f"{phrase:.10}" # Lorem ipsu

start = "début"
end = "fin"
f"{start} {end:=>10}" # début ====== fin
f"{end:=^15}" # ===== fin =====

Manipulation de chaîne de caractères

De nombreuses méthodes de manipulation des chaînes de caractères sont détaillées dans la documentation.

chaine = "Bonjour"
chaine.upper() # 'BONJOUR'
chaine.lower() # 'bonjour'

chaine = "bonjour tout le monde"
chaine.capitalize() # 'Bonjour tout le monde'
chaine.title() # 'Bonjour Tout Le Monde'

"bonjour".replace("jour", "soir") # 'bonsoir'
"  bonjour  ".strip() # 'bonjour'
"  bonjour  ".strip(" ujor") # 'bon'
"  bonjour  ".rstrip(" ujor") # '  bon'
"  bonjour  ".lstrip(" ujor") # 'bonjour  '

"1, 2, 3".split(", ") # ['1', '2', '3']
", ".join(['1', '2', '3']) # '1, 2, 3'

"9".zfill(4) # '0009'

"bonjour".islower() # True
"bonjour".isupper() # False
"Bonjour".iscapitalize() # True
"Bonjour".istitle() # True
"10".isdigit() # True

"bonjour".count("jour") # 1
"bonjour".index("jour") # 3
"bonjour".index("soir") # ValueError
"bonjour".find("jour") # 3
"bonjour".find("soir") # -1

"bonjour".startswith("bon") # True
"bonjour".endswith("jour") # True
chaine.startswith("http", "https", "www")

Collections

En Python des objets peuvent être immuables ou muables. Les objets immuables ne peuvent pas être modifiés après leur création : int, float, bool, str, tuple. Les objets muables peuvent être modifiés après leur création : list, set, dict.

Les collections natives sont les suivantes :

Note

Les fonctions natives applicables sur les collections :

len(object) # nombre d'éléments
min(object) # plus petit élément
max(object) # plus grand élément
sum(object) # somme des éléments

Listes, tuples et set

# Définir une liste / un tuple / un set
my_list = [1, 2, 3]
my_tuple = (1, 2, 3)
my_set = {1, 2, 3}

# Convertir une liste / un tuple
list(my_tuple) # [1, 2, 3]
tuple(my_liste) # (1, 2, 3)
set(my_liste) # {1, 2, 3}

# Ajouter et enlever des éléments
my_list.append(4) # [1, 2, 3, 4]
my_list.extend([4, 5]) # [1, 2, 3, 4, 4, 5]
my_list.remove(4) # [1, 2, 3, 4, 5]

# Ajouter et enlever des éléments sur les sets
my_set.add(3) # {1, 2, 3}
my_set.update([3, 4]) # {1, 2, 3, 4}
my_set.remove(4) # {1, 2, 3} erreur si non existant
my_set.discard(4) # {1, 2, 3} pas d'erreur si non existant

# Opérations sur les sets
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
a | b # a.union(b) -> {1, 2, 3, 4, 5, 6}
a & b # a.intersection(b) -> {3, 4}
a - b # a.difference(b) -> {1, 2}
a ^ b # a.symmetric_difference(b) -> {1, 2, 5, 6}

# Sélectionner des éléments avec les slices
my_list[0] # 1
my_list[-1] # 5
my_list[0:2] # [1, 2]
my_list[:] # [1, 2, 3, 4, 5]
my_list[1::2] # [2, 4]
my_list[::-1] # [5, 4, 3, 2, 1]
my_list[0][0] # listes imbriquées

# Copier une liste
copy_list = my_list[:]
copy_list = list(my_list)
copy_list = my_list.copy()

# Trier une liste
my_list.sort() # modifie my_list
sorted(my_list) # retourne une nouvelle liste

# Autres méthodes
my_list.index(1) # 0
my_list.count(1) # 1
my_list.pop(1) # [2, 3, 4, 5]
my_list.clear() # []

# Opérateurs d'appartenance
"bon" in "bonjour"
1 in my_list
1 not in my_list

# Fonctions all et any
all(my_list) # vrai si tous les éléments sont vrai (and)
any(my_list) # vrai si un élément est vrai (or)

# Parcourir une liste avec enumerate(iterable, start)
for i, item in enumerate(my_list, 1):
    print(i, item)

# Générer une liste avec range(start, stop, step)
for i in range(0, 10, 1):
    print(i)

# Aplatir une liste
my_list = [[1, 7, 3], [3, 4], [1, 3, 3]]
my_list = sum(my_list, []) # [1, 7, 3, 3, 4, 1, 3, 3]

# Enlever les doublons
my_list = list(set(my_list) # [1, 7, 3, 4]

Dictionnaires

# Définir un dictionnaire
my_dict = {"1": "a", "2": "b", "3": "c"}

# Sélectionner une clé
my_dict.get(1, "La clé n'existe pas")
my_dict["1"]

# Ajouter ou modifier une clé
my_dict["1"] = "A"

# Enlever une clé
del my_dict["1"]

# Parcourir un dictionnaire (clés/valeurs)
my_dict.keys()
my_dict.values()

for key in my_dict:
    print(f"{key} - {my_dict[key]}")

for key, value in my_dict.items():
    print(f"{key} - {value}")

# Parcourir un dictionnaire avec enumerate(iterable, start)
for i, (key, value) in enumerate(my_dict.items(), 1):
    print(i, key, value)

# Créer un dictionnaire à partir de deux listes
ids = [1, 2, 3]
values = ["a", "b", "c"]
my_dict = dict(zip(ids, values))

# Inverser les clés et valeurs d'un dictionnaire
my_dict = dict(zip(my_dict.values(), my_dict.keys()))

Collections spécialisées

Les collections spécialisées sont détaillées dans la documentation du module collections :

Dans un defaultdict, si une clé n'existe pas elle est initialisée avec une valeur par défaut.

from collections import defaultdict

my_dict = defaultdict(int)
my_dict["a"] += 1

Dans un Counter, si une clé existe sa valeur est incrémentée.

from collections import Counter

my_count = Counter("abcdabcaba") # {'a': 4, 'b': 3, 'c': 2, 'd': 1}
my_count.most_common(2) # {('a': 4), ('b': 3)}
my_count.elements() # itération sur les valeurs selon leur nombre

Dans un OrderDict, les entrées du dictionnaire gardent l'ordre dans lequel elles ont été ajoutées.

from collections import OrderDict

my_dict = OrderDict()
my_dict["a"] = 1
my_dict["b"] = 2
my_dict["c"] = 3

Fonctions d'itérateurs

Les fonctions d'itérateurs sont détaillées dans la documentation du module itertools :

Unpacking

L'unpacking consiste à extraire les valeurs d'un itérable dans des variables.

# Unpacking de listes avec l'opérateur *
my_list = [1, 2, 3]
a, b, c = my_list # a=1 b=2 c=3

my_list = [1, 2, 3, 4, 5]
a, b, *c = my_list # a=1 b=2 c=[3, 4, 5]
a, *b, c = my_list # a=1 b=[2, 3, 4] c=5

my_list = [1, 2]
a, *_, c = my_list # a=1 _=[] c=2

# Unpacking de dictionnaires avec l'opérateur **
dict1 = {"a": 1, "b": 2, "c": 3}
dict2 = {"d": 4, "e": 5}
my_dict = {**dict1, **dict2} # {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}

L'unpacking dans les fonctions, args (positional arguments) et kwargs (keyword arguments) :

def my_function(*args, **kwargs):
    print(type(args), args) # <class 'tuple'> (1, 2, 3, 4, 5)
    print(type(kwargs), kwargs) # <class 'dict'> {'nom': 'Python', 'age': 30}

    args_result = 0
    for i in args:
        args_result += i
    print(args_result) # 15

    kwargs_result = ""
    for key, value in kwargs.items():
        kwargs_result += f"{key}={value} "
    print(kwargs_result) # 'nom=Python age=30'

nombres = [1, 2, 3, 4, 5]
my_function(*nombres, nom="Python", age=30)
my_function(1, 2, 3, 4, 5, nom="Python", age=30)


def my_function(nom, age):
    print(f"nom={nom}, age={age}") # 'nom=Python, age=30'

params = {"nom":"Python", "age":30}
my_function(**params)
my_function(nom="Python", age=30)

Opérateurs

Opérateurs mathématiques

Effectuer des opérations mathématiques.

10 + 2 # 12
10 - 2 # 8
10 * 2 # 20
10 / 2 # 5.0

"10" + "2" # 102
"10" * 2 # 1010

10 % 2 # 0
10 % 8 # 2

10 // 2 # 5
10 // 3 # 3
10 / 3 # 3.33333333333335

10 ** 2 # 100

Les fonctions mathématiques plus avancées sont dans le module math : sqrt, floor, ceil...

Opérateurs d'assignation

Affecter des valeurs aux variables.

i = 1
i += 1
i -= 1
i *= 1
i /= 1
i %= 1
i //= 1
i **= 1

Opérateurs de comparaison

Comparer deux valeurs.

>
<
>=
<=
==
!=

Opérateurs logiques

Combiner plusieurs conditions.

and
or
not

Opérateurs d'identité

Vérifier si deux objets sont situés sur la même adresse mémoire.

is
is not

Opérateurs d'appartenance

Vérifier si un objet est présent dans un itérable.

in
not in

Structures de contrôle de flux

Structures conditionnelles

La structure if, elif, else :

if age > 0 and age < 18:
    print("Vous êtes mineur")
elif age >= 18:
    print("Vous êtes majeur")
else:
    print("Àge incorrect")

Il est possible de chaîner les comparateurs :

if 0 < age < 18:
    print("Vous êtes mineur")

Il est possible d'utiliser un opérateur ternaire :

# <expression> if <condition> else <expression>
r = "majeur" if age >= 18 else "mineur"

Filtrage par motif

La structure match, case :

match subject:
    case <pattern_1>:
        <action_1>
    case <pattern_2>:
        <action_2>
    case <pattern_3>:
        <action_3>
    case _:
        <action_wildcard>

Les patterns peuvent contenir des conditions if, des assignations as :

action = input("Que voulez-vous faire ? ")
match action.split():
    case ["Jouer"]:
        print("Début du jeu")
    case ["Quitter"]:
        print("Fin du jeu")
    case ["Avancer", ("Nord" | "Sud" | "Est" | "Ouest") as orientation]:
        print(f"Vous avancez d'une case vers le {orientation}")
    case ["Attaquer", ennemy] if ennemy in ["E1", "E2", "E3"]:
        print(f"Vous attaquez {ennemy} !")
    case _:
        print("Veuillez entrer une commande valide...")

Boucles while et for

La boucle while :

i = 0
while i < 10000:
    print(i)
    i += 1

import time
while True:
    print("en cours...")
    time.sleep(500)

La boucle for :

for i in [0, 1, 2, 3, 4, 5]:
    print(i)

for i in range(5):
    print(i)

Les instructions suivantes permettent de modifier le comportement d'une boucle :

Par exemple :

for i in range(10):
    if i % 10 == 0:
        print("boucle interrompue")
        break
    print(i)
else:
    print("boucle exécutée en entier")

Compréhensions de liste

Les compréhensions de liste offrent la possibilité de faire des opérations sur les listes en une seule ligne. Cela évite de passer par des boucles ou par des fonctions telles que map, filter...

# [<expression> for <element> in <iterable> if <condition>]
# [<expression> if <condition> else <expression> for <element> in <iterable>]

liste = [-3, -2, -1, 0, 1, 2, 3]
resultat = [i for i in liste] # [-3, -2, -1, 0, 1, 2, 3]
resultat = [i * 2 for i in liste] # [-6, -4, -2, 0, 2, 4, 6]
resultat = [i * 2 for i in liste if i > 0] # [2, 4, 6]
resultat = [i * 2 if i > 0 else i for i in liste] # [-3, -2, -1, 0, 2, 4, 6]

# Ancienne méthode
resultat = map(lambda x: x * 2, liste)
resultat = filter(lambda x: x > 0, liste)

La même notion existe pour les sets et les dictionnaires :

my_set = {i for i in liste}
my_dict = {i: i for i in liste}

Itérateurs et générateurs

Un itérateur est créé avec la fonction iter sur un objet itérable et permet de consommer les éléments avec la fonction next.

iterator = iter("hello world")
#iterator.next()
next(iterator)

Un générateur permet de créer des itérateurs grâce à l'instruction yield.

def generator(n):
    for i in range(1, n + 1):
        yield i

iterator = generator(10)
#iterator.next()
for i in iterator:
    print(i)

Un générateur peut aussi être créé avec la syntaxe des compréhensions de liste mais contrairement à ces dernières les valeurs sont générées à la volée au lieu d'être stockées :

my_generator = (i for i in range(10))

Fonctions input et print

La fonction input retourne la valeur saisie par l'utilisateur sous la forme d'une chaîne de caractères.

>>> age = input("Quel âge avez-vous ? ")
Quel âge avez-vous ? 26
>>> print(age)
26

La fonction print affiche un objet dans le terminal.

>>> print("hello world")
hello world
>>> print("hello", "world")
hello world
>>> print("hello", "world", sep=" ")
hello world
>>> liste = ["hello", "world"]
>>> print(*liste, sep=" ")
hello world

La fonction pprint affiche un objet dans le terminal de manière lisible par un humain. Utile notamment pour afficher des dictionnaires ou des collections importantes.

from pprint import pprint
pprint(object)

Fichiers

Les mode d'ouverture d'un fichier sont :

Cas d'un fichier texte :

chemin = "/home/user/fichier"

# Ancienne méthode sans l'instruction with
f = open(chemin, "r")
f.close()

# Lecture du fichier
with open(chemin, "r") as f:
    print(f.read())
    f.seek(0) # déplacement pointeur au début du fichier
    print(f.read())

# Écriture du fichier
with open(chemin, "w") as f:
    f.write("Bonjour")

Gestion des erreurs

Le bloc else est exécuté si aucune exception n'est capturé par les blocs except. Le bloc finally est exécuté systématiquement.

try:
    resultat = a / b
except ZeroDivisionError:
    print("Division par zero impossible")
except TypeError as e:
    print("Erreur:", e)
else:
    print(resultat)
finally:
    print("Fin")

Fonctions

Les paramètres sont les noms des variables qui sont définies dans la fonction, les arguments sont les valeurs qu'on va envoyer à ces paramètres.

def addition(a, b=10):
    return a + b

addition(10) # 20
addition(10, 5) # 15
addition(b=10, a=5) # 15

Annotations de type

Les annotations de type servent à spécifier le type des variables et à documenter le code.

a: int = 0

def addition(a: int, b: int = 10) -> int:
    return a + b

def sequence(n: int | None) -> list[int | float]:
    return range(n) if n else [1.0, 2.0, 3.0]

Avec les annotations de type, les éditeurs de code peuvent afficher des avertissements si des erreurs de typage sont détectées.

Il est aussi possible de vérifier le code avec des utilitaires comme mypy.

Fonctions anonymes

Une fonction anonyme (lambda) est une fonction jetable. Elle n'a pas vocation à être réutilisée.

# Affectation d'un lambda à une variable
addition = lambda a, b: a + b

# Utilisation d'un lambda pour trier une liste
liste.sort(key=lambda x: x[1])

Note

La fonction partial permet de créer une fonction à partir d'une fonction existante en lui passant des arguments prédéfinis :

from functools import partial

increment = partial(addition, b=1)
increment(10) # 11

Attention

Les lambda évaluent les variables à l'exécution alors que les partial les évaluent à la création de la fonction.

Espaces de nommage

Notion d'espace de nommage global (racine du script) et local (dans un bloc de code, une fonction...).

globals() # dictionnaire des variables globales
locals() # dictionnaire des variables locales

Packages et modules

Un package est un répertoire qui contient des modules ainsi qu'un fichier optionnel __init__.py qui gère l'initialisation du package (imports, docstring...).

Un module est un fichier qui contient du code python.

Pour importer un module :

import package.module
from package import module

import module
from module import function

Pour exécuter du code dans un module lorsqu'il est lancé directement mais pas lorsqu'il est importé :

if __name__ == "__main__":

Pour trouver le chemin d'un module :

import os
module_path = os.__file__

current_path = __file__
current_dir_path = os.path.dirname(__file__)

Python Path

Un module peut être chargé par l'interpréteur Python s'il est situé dans l'un des répertoires du Python Path.

import sys
sys.path.append("/home/user/modules")
print(sys.path)

Le Python Path peut être modifié via la variable d'environnement PYTHONPATH :

# bash : .bashrc
export PYTHONPATH="/home/user/modules":"/home/user/modules2"

# fish
set -xg PYTHONPATH /home/user/modules /home/user/modules2

Commentaires et documentation

# Commentaire sur une ligne

"""Commentaire multiligne
avec guillemets doubles
ou guillemets simples
"""

La docstring permet de documenter les modules, les fonctions/méthodes et les classes directement dans le code. Il s'agit de commentaires qui suivent une synthaxe particulière. Il existe plusieurs formats de docstring : Epytext, reST, Google.

"""Description du module.

Description détaillée du module...
Description détaillée du module...

Utilisation :

my_class = MyClass()
result = my_class.my_method()
"""

class MyClass:
    """Description de la classe.

    Description détaillée de la classe...
    Description détaillée de la classe...

    Attributes:
        a (int): Description de l'attribut a.
        b (int): Description de l'attribut b.
    """

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def my_method(nom, age):
        """Description de la méthode.

        Args:
            nom (str): Le nom de l'utilisateur.
            age (int): L'âge de l'utilisateur.

        Returns:
            list: Description de la valeur retournée.

        Raises:
            ErrorName: Description de l'erreur.
        """
        return None

La documentation peut ensuite être affichée avec la variable __doc__ ou avec la fonction help.

Introspection

Le module os permet d'obtenir des informations sur le système d'exploitation. Il permet aussi de manipuler les fichiers et répertoires mais il est préférable d'utiliser le module de plus haut niveau pathlib dans ce cas.

import os

os.environ # variables d'environnement
os.getenv("HOME") # variable d'environnement HOME
os.cpu_count() # nombre de CPU

Le module sys permet d'obtenir des informations sur l’interpréteur Python.

import sys

sys.platform # système d'exploitation
sys.version # version de l'interpréteur
sys.version_info # version de l'interpréteur
sys.path # liste des chemins de recherche des modules
sys.argv # arguments passé au script
sys.exit() # stop le programme

La fonction help affiche la documentation d'un module.

help(module)
help(module.function)

# Avec la variable __doc__
module.__doc__
module.function.__doc__

La fonction dir affiche tous les attributs et les méthodes d'un objet.

dir() # éléments de l'espace local 
dir(module) # éléments d'un module 
dir(object) # éléments d'un objet

La fonction type retourne le type d'un objet.

type(object) # str, bool, int...

La fonction callable vérifie si un objet peut être appelé (fonction ou méthode) ou non (attribut).

callable(object)

Les fonctions isinstance et issubclass vérifient également le type d'un objet.

isinstance(object, (int, float)) # comparaison avec un tuple
issubclass(object, int | float) # comparaison avec une union

La fonction id retourne l'adresse en mémoire d'un objet.

id(object) # 4514223600

La fonction repr affiche la représentation interne d'un objet destinée au développeur. La fonction str affiche la représentation textuelle d'un objet destinée à l'utilisateur.

repr(now) # 'datetime.datetime(2019, 3, 29, 1, 29, 23, 211924)'
str(now) # '2019-03-29 01:29:23.211924'

Un commentaire sur un de mes articles ? Commencez une discussion sur ma liste de diffusion en envoyant un email à ~nora/public-inbox@lists.sr.ht [règles]