Aller au contenu

Aide-mémoire — Spécificités Python#

courant

Tout est objet#

Fonctions, classes, modules sont des objets manipulables (assignables, passables en argument).

def saluer():
    print("salut")

f = saluer   # pas d'appel, juste une référence
f()           # appelle la fonction

Portée des variables — règle LEGB#

Ordre de résolution d'un nom : Local → Enclosing (fonction englobante) → Global → Built-in.

x = "global"

def externe():
    x = "enclosing"
    def interne():
        print(x)   # "enclosing" (pas "global")
    interne()

def modifie_global():
    global x
    x = "modifié"

def modifie_enclosing():
    x = "enclosing"
    def interne():
        nonlocal x
        x = "modifié"
    interne()

Passage d'arguments#

Tout est passé par référence à l'objet (pas par copie, pas par pointeur au sens C).

def ajoute(liste):
    liste.append(4)     # modifie l'objet original (mutable)

def reassigne(liste):
    liste = [9, 9, 9]    # ne modifie pas l'original (rebind local)

l = [1, 2, 3]
ajoute(l)      # l == [1, 2, 3, 4]
reassigne(l)    # l == [1, 2, 3, 4] (inchangé)

Copie d'objets : référence, superficielle ou profonde#

Pour créer une vraie copie distincte d'une collection sans partager la référence mémoire :

import copy

a = [[1, 2], [3, 4]]

# 1. Copie par référence (même objet)
b = a
b.append([5, 6])          # modifie a aussi !

# 2. Copie superficielle / shallow copy (nouvelle liste, mais éléments partagés)
c = a.copy()              # ou list(a)
c[0].append(99)           # modifie a[0] car les sous-listes sont partagées !

# 3. Copie profonde / deep copy (duplication récursive complète)
d = copy.deepcopy(a)
d[0].append(100)          # a reste parfaitement intact

Piège classique en calcul / modélisation

Ne jamais créer une matrice avec [[0] * 3] * 3 : cela répète 3 fois la même sous-liste en mémoire ! Utiliser une compréhension : [[0 for _ in range(3)] for _ in range(3)].

Piège : argument par défaut mutable

Argument par défaut mutable : évalué une seule fois, à la définition de la fonction.

def ajouter(x, l=[]):   # piège : la même liste est réutilisée entre appels
    l.append(x)
    return l

def ajouter(x, l=None):  # correct
    if l is None:
        l = []
    l.append(x)
    return l

Duck typing#

def parcourir(objet):
    for item in objet:   # fonctionne avec toute classe implémentant __iter__
        print(item)

Égalité vs identité#

a = [1, 2]
b = [1, 2]
a == b   # True  (mêmes valeurs de contenu)
a is b   # False (objets distincts en mémoire : id(a) != id(b))

Règle absolue

Ne jamais utiliser is pour comparer des nombres ou des chaînes (a == 5, jamais a is 5). Réserver is aux singletons, en particulier is None et is not None.

Pour aller plus loin : Interning CPython & GIL

CPython conserve en mémoire un cache pré-alloué pour les petits entiers (de -5 à 256) et certaines chaînes littérales. Ainsi, 256 is 256 renvoie True alors que 257 is 257 peut renvoyer False. C'est un détail interne d'optimisation de l'interpréteur auquel il ne faut jamais se fier.

Dans l'implémentation standard CPython, un verrou interne (le GIL) garantit qu'un seul thread de code bytecode s'exécute à la fois. Le multi-threading natif n'accélère donc pas les calculs lourds (CPU-bound) ; pour paralléliser des calculs sur plusieurs cœurs, privilégier multiprocessing.