Expressions
Expressions multilignes
Une expression peut traverser un saut de ligne lorsqu'elle est entourée de parenthèses. À l'intérieur, les newlines et l'indentation ne terminent pas l'expression :
total = (
price
* quantity
* (1 + tax)
)
valid = (
x > 0
and x < 100
and enabled
)
Sans parenthèses, un newline termine l'expression :
result = 1 + 2 +
3 + 4 # erreur de parsing
result = (
1 + 2 +
3 + 4
)
# ⇒ 10
Les collections et les blocs acceptent déjà les newlines, sans parenthèses supplémentaires.
Appels et lambdas
Un saut de ligne est accepté après (, entre les arguments ou paramètres, et avant ) :
server = create_server(
"192.168.1.1",
3000,
ssl=True,
workers=4,
)
transform = (
x,
y,
scale=1.0,
) => {
(x + y) * scale
}
Une virgule finale est facultative. Les arguments positionnels restent placés avant les arguments nommés, quelle que soit leur ligne.
Les parenthèses suspendent la fin de ligne. Le parser attend la fermeture avant de reprendre le temps normal.
Opérateurs
Arithmétique
10 + 5 - 2 * 3
# ⇒ 9
10 / 4
# ⇒ 2.5
10 // 4
# ⇒ 2
-10 // 4
# ⇒ -3
17 % 5
# ⇒ 2
2 ** 10
# ⇒ 1024
/ produit toujours un float, même lorsque la division tombe juste. // effectue une division plancher : elle
arrondit vers le bas, pas vers zéro.
L'exponentiation lie plus fortement que l'opérateur unaire :
-2 ** 2
# ⇒ -4
(-2) ** 2
# ⇒ 4
Une base négative élevée à une puissance fractionnaire n'a pas de racine réelle : le résultat est la racine principale, donc un complexe.
(-8) ** (1/3)
# ⇒ (1+1.732050807568877j)
Les entiers ont une précision arbitraire. La VM promeut automatiquement un résultat qui dépasse sa représentation
compacte et le redescend lorsque la valeur revient dans sa plage ; ce changement de stockage n'affecte pas le type
visible. Les décalages suivent la même règle : 1 << 64 vaut 18446744073709551616, pas 1. Un compte de décalage
négatif est un ValueError ; >> sature au-delà de la largeur (0, ou -1 pour un négatif) là où <<, qui doit
matérialiser chaque bit déplacé, refuse un compte que rien ne pourrait allouer.
Comparaison, appartenance et identité
5 == 5
5 != 3
3 < 5
1 < x < 10
2 in list(1, 2, 3)
"x" not in dict(a=1, b=2)
"cat" in "catnip"
value is None
value is not None
== compare les valeurs. is compare l'identité : deux noms liés au même objet satisfont a is b, même si le type
définit une égalité personnalisée. Les comparaisons peuvent être chaînées ; chaque opérande intermédiaire n'est évalué
qu'une fois.
Les structures peuvent surcharger les comparaisons et l'appartenance. Voir Structures.
Logique
ready = loaded and valid
fallback = cached or computed
disabled = not enabled
and et or court-circuitent : la partie droite n'est évaluée que si elle peut changer le résultat. En Catnip, ils
retournent un booléen, pas l'un de leurs opérandes. La table complète de truthiness se trouve dans
Types de données.
Nil-coalescing
?? retourne son opérande gauche lorsqu'il n'est pas None; sinon, il évalue et retourne la partie droite :
42 ?? 0
# ⇒ 42
None ?? 0
# ⇒ 0
None ?? None ?? 3
# ⇒ 3
False ?? True
# ⇒ False
L'opérateur teste uniquement None, pas la truthiness. Il court-circuite, mais ne capture pas les erreurs produites
pendant l'évaluation de la partie gauche : mapping["missing"] ?? default lève d'abord KeyError.
Opérateurs bitwise
0xFF & 0x0F
# ⇒ 15
0xF0 | 0x0F
# ⇒ 255
0xFF ^ 0x0F
# ⇒ 240
~0
# ⇒ -1
Sur les entiers, &, |, ^, <<, >> et ~ opèrent bit par bit. Sinon, ils délèguent au protocole de l'opérande :
un tableau NumPy peut les appliquer élément par élément et une structure peut définir op &, op |, op ^ ou op ~.
Priorité
Les parenthèses restent la forme explicite lorsque plusieurs familles se rencontrent. Sans parenthèses, les groupes principaux vont du plus fort au plus faible :
| Priorité | Opérateurs |
|---|---|
| Forte | ** |
+x, -x, ~x |
|
*, /, //, % |
|
+, - |
|
<<, >>, puis &, ^, \| |
|
comparaisons, in, is |
|
not, puis and, puis or |
|
| Faible | ?? |
** est associative à droite : 2 ** 3 ** 2 vaut 2 ** (3 ** 2). Une comparaison chaînée n'est pas une suite de
booléens comparés entre eux : a < b < c évalue b une fois et exige les deux relations.
2 + 3 * 4
# ⇒ 14
(2 + 3) * 4
# ⇒ 20
True or None ?? False
# ⇒ True
Quand la lecture du code dépend de plusieurs lignes du tableau, des parenthèses rendent le contrat local et évitent de faire reposer une modification sur un souvenir de priorité.
Attributs et chaînage
. lit un attribut ou appelle une méthode. Les accès peuvent être chaînés tant que chaque étape fournit l'objet attendu
par la suivante :
host = config.database.host
result = client.connect(timeout=5).receive().decode()
Une assignation peut cibler le dernier attribut :
config.database.host = "localhost"
Une erreur à une étape interrompt la chaîne ; Catnip n'ajoute pas de propagation implicite de None.
L'appel de méthode lie automatiquement l'instance à son premier paramètre self. Lire le même attribut sans parenthèses
retourne la méthode liée, qui peut ensuite être stockée ou passée en callback :
send = client.send
send(message)
Attributs d'introspection
Les attributs qui mènent d'une valeur au graphe des types ou à une portée ne se lisent ni ne s'écrivent depuis Catnip. La forme est refusée à la lecture du fichier, avec sa position :
[].__class__ # SyntaxError: special attribute '__class__' is not accessible from Catnip
La liste : __bases__, __builtins__, __class__, __closure__, __code__, __dict__, __func__,
__getattribute__, __globals__, __import__, __init__, __loader__, __module__, __mro__, __new__,
__objclass__, __reduce__, __reduce_ex__, __self__, __spec__, __subclasses__, __wrapped__.
La raison est ce qu'un objet Python donne quand on le remonte : [].__class__.__bases__[0].__subclasses__() part d'un
littéral de liste et atteint toute classe chargée dans l'interpréteur, puis __init__.__globals__["__builtins__"]
atteint open, exec et __import__. La chaîne commence dans le langage, donc aucun choix de globals ne la ferme —
seul le refus des maillons la coupe.
Un attribut qui s'écrit avec quatre soulignements n'avait pas prévu d'être lu par toi.
Pour lire un nom de type, typeof(x) remplace x.__class__.__name__.
Ces noms ne se déclarent pas non plus — champ ou méthode de structure, méthode de trait, membre d'énumération, variante d'union ou champ de variante. Le membre serait construit sans jamais pouvoir être relu :
struct P { __class__ } # SyntaxError: cannot declare '__class__': the name would not be readable back
Les autres noms encadrés de soulignements restent accessibles, parce qu'ils désignent des protocoles et non un chemin de
navigation : x.__len__(), col.__eq__(other), shape.__geo_interface__. Le runtime, lui, continue d'appeler les
siens pour son compte — with invoque __enter__ et __exit__, une boucle for invoque __iter__, la troncature de
vérité invoque __bool__ — et ces appels-là ne passent pas par la forme écrite.
Indexation
object[index] transmet l'index au protocole du type :
numbers = list(10, 20, 30, 40)
numbers[0]
# ⇒ 10
numbers[-1]
# ⇒ 40
person = dict(name="Alice", age=30)
person["name"]
# ⇒ "Alice"
"catnip"[2]
# ⇒ "t"
L'index peut être une expression. Un index négatif compte depuis la fin pour les séquences qui suivent la convention Python.
Une assignation indexée utilise le protocole symétrique du type :
numbers[1] = 99
person["age"] = 31
Elle modifie l'objet indexé ; elle ne crée pas une nouvelle liaison pour numbers ou person. Les chaînes et tuples
refusent cette opération parce qu'ils sont immuables.
Slicing
Un slice utilise [start:stop:step]. Chaque composant est facultatif ; start est inclus et stop exclu :
values = list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
values[2:6]
# ⇒ [2, 3, 4, 5]
values[:3]
# ⇒ [0, 1, 2]
values[7:]
# ⇒ [7, 8, 9]
values[:]
# ⇒ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
step choisit l'intervalle entre deux éléments :
values[::2]
# ⇒ [0, 2, 4, 6, 8]
values[1:8:2]
# ⇒ [1, 3, 5, 7]
Les bornes négatives comptent depuis la fin. Un pas négatif inverse le sens du parcours :
values[-3:]
# ⇒ [7, 8, 9]
values[::-1]
# ⇒ [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
"kayak" == "kayak"[::-1]
# ⇒ True
Une borne hors limites est ajustée et peut produire une séquence vide. Un pas égal à zéro lève ValueError.
values[20:30]
# ⇒ []
values[::0] # ValueError
Le slicing fonctionne sur les listes, chaînes et objets hôtes qui acceptent un objet slice. Il préserve le type de séquence selon le protocole de cet objet.
Notation membre .[...]
La notation .[start:stop:step] exprime le même slice sous forme de membre. Elle permet de poursuivre directement une
expression ou un broadcast :
(list(1, 2, 3) + list(4, 5, 6)).[1:5]
# ⇒ [2, 3, 4, 5]
list(10, 20, 30, 40).[:3].[* 2]
# ⇒ [20, 40, 60]
data.[:] et data[:] ont la même sémantique. Le point ne change ni les bornes ni la copie ; il rend la chaîne
d'opérations explicite.
Accès imbriqué
Deux paires de crochets effectuent deux accès successifs :
matrix = list(
list(1, 2, 3),
list(4, 5, 6),
)
matrix[1][2]
# ⇒ 6
matrix[0:2][1]
# ⇒ [4, 5, 6]
Le premier résultat doit donc lui-même être indexable.
Indexation multiple
Plusieurs positions séparées par des virgules construisent une seule clé tuple. object[a, b] équivaut à
object[(a, b)] et appelle une fois __getitem__((a, b)). C'est la forme utilisée par NumPy et les objets
N-dimensionnels :
numpy = import("numpy")
array = numpy.array(list(
list(1, 2, 3),
list(4, 5, 6),
))
array[1, 2]
# ⇒ 6
array[0:2, 1]
# ⇒ [2, 5]
array[1, 2] = 99
Chaque position accepte une expression ou un slice. Un seul index n'est jamais enveloppé dans un tuple : object[index]
reste un accès simple.
object[a, b] et object[a][b] ne sont donc pas interchangeables. La première forme transmet (a, b) en un appel ; la
seconde transmet a, puis b au résultat.
Dans les crochets d'accès, la virgule décrit une coordonnée. Deux crochets successifs décrivent un trajet.