Nous allons maintenant étudier les principaux objets utilisés en Python, que nous appellerons des types : il s’agit d’objets relativement simples, comparés à ceux que l’on peut trouver ou construire dans des modules spécialisés, et ces types représentent des objets assez intuitifs :
Nous ne détaillerons pas toutes les subtilités ni les usages des types tout de suite, et nous nous contenterons de jeter un œil à leur définition, à leurs principales caractéristiques ainsi que leur comportement en fonction des opérateurs.
Commençons par présenter deux fonctions que nous utiliserons continuellement :
Retourne le type de object au moyen un objet de type.
La variable classinfo peut être une classe ou un tuple de classes. La fonction retourne True si object est une instance d’une des classes de la liste ou d’une sous-classe.
Nous verrons quelques exemples explicites dans les sections qui suivent.
On préférera réserver l’usage de type() au mode interactif, quand on veut vérifier le type d’un objet : en effet, la fonction isinstance() est plus robuste car elle vérifie aussi si l’objet ne dérive pas d’une sous-classe des classes indiquées. De plus, comme elle retourne un booléen, la fonction est plus “propre” pour écrire un test. Comparer les écritures suivantes :
>>> type(1) == int
True
>>> isinstance(1, int) is True
True
On aurait même pu écrire omettre le is True comme la syntaxe est assez claire.
Note
type() retourne en fait l’objet de classe et permet donc de construire directement un objet du même type que son argument :
>>> type(1)('102')
102
qui a bien le même effet que :
>>> int('102')
102
Cette construction peut être utile si on veut convertir un objet dans le même type qu’un second, sans connaitre ce type à l’avance.
Cette section suppose que le lecteur possède quelques notions de mathématiques, même si certains aspects un peu plus avancés sont traités afin de viser l’exhaustivité.
Note
Tous les objets nombres dérivent de la classe numbers.Number.
Les flottants, ou nombres réels, représentent les nombres à virgule. Ils peuvent être représentés de plusieurs manières :
La fonction float() permet de convertir un nombre en flottant.
La précision de ces nombres dépend entièrement de la machine sur laquelle est exécutée Python, même si cela n’est pas affiché explicitement à l’écran.
>>> 1.1
1.1
>>> type(1.1)
<class 'float'>
>>> float(1)
1.0
>>> type(1.0)
<class 'float'>
>>> 2e-2
0.02
>>> type(2e-2)
<class 'float'>
Un nombre entier dont la partie décimale est explicitement représentée est considéré comme un nombre à virgule. Cela pouvait être utile dans les versions précédentes mais beaucoup moins dans la version 3.
Nous remarquons un effet de bord indésirable lorsque l’on entre 1.1 : il s’agit d’un problème commun à tous les langages dès que l’on utilise des nombres flottants. Ceci est dû à la manière dont les nombres sont gérés.
Warning
Il faut faire très attention à utiliser le point et non la virgule pour les nombres décimaux : deux nombres séparés par une virgule seront considérés comme un tuple.
Warning
Les anciennes versions de Python peuvent afficher de manière incorrecte les nombres flottants à cause d’erreurs d’arrondis :
>>> 1.1
1.1000000000000001
Il s’agit des nombres entiers (“integer” en anglais, abrégé en “int”), positifs ou négatifs. Leur taille a pour seule limite la mémoire virtuelle du système (là où les nombres d’autres langages sont limités, par exemple les int en C ne peuvent dépasser 32 bits).
La fonction int() permet de convertir un objet en nombre entier. Si on lui transmet un nombre flottant, alors elle ne garde que la partie entière.
>>> 1
1
>>> type(1)
<class 'int'>
>>> 23923 * 985
23564155
>>> 1234567899876543212345678998765432123456789
1234567899876543212345678998765432123456789
>>> int(3.5)
3
Une manière de s’assurer qu’un nombre est bien entier est d’utiliser l’expression n == int(n), qui ne sera vraie que si n est un nombre entier à la base (même écrit sous forme de flottant) :
>>> a, b, c = 1, 1.0, 1.5
>>> a == int(a)
True
>>> b == int(b)
True
>>> c == int(c)
False
Note
À moins que vous n’étudiez les sciences (et encore), cette section sera pour ainsi dire inutile et vous pouvez l’ignorer sans états d’âme.
Les nombres complexes (ou imaginaires) ont été inventés pour résoudre certains problèmes mathématiques. Depuis, ils sont utilisés dans de nombreux domaines (ingénierie, physique...). Un nombre complexe est de la forme a + bj [2], où a et b sont des réels [3] [4]. La forme utilisant un J est aussi acceptée.
On peut obtenir un nombre complexe grâce à la fonction complex().
>>> c = 2+3j
>>> c
(2+3j)
>>> type(c)
<class 'complex'>
>>> 2.2j
2.2j
>>> 1+2J
(1+2j)
>>> complex(3.5)
(3.5+0j)
Les parties réelles et imaginaires d’un nombre complexe sont accessibles avec les attributs real et imag (qui renvoient des float). Enfin, le conjugué s’obtient avec la méthode conjugate().
>>> c.real
2.0
>>> c.imag
3.0
>>> c.conjugate()
(2-3j)
Note
Il est impossible d’écrire simplement j car l’interpréteur le considère comme l’identifiant d’une variable. Le nombre purement imaginaire unité s’écrit donc 1j.
Il s’agit d’une classe dérivée de int.
Les booléens indiquent une valeur de vérité : il n’existe ainsi que deux valeurs possibles : vrai (1, qui correspond à l’objet True) ou faux (0, soit False). La conversion en booléen se fait avec la fonction bool().
Nous reviendrons sur ces objets et leur utilisation dans le chapitre Structures de contrôle.
La plupart des opérations numériques classiques (addition, soustraction, multiplication...) existent en Python et sont accessibles par les opérateurs idoines :
Les comparaisons sont celles usuellement définies avec les nombres : un nombre est supérieur à un autre s’il est plus grand...
Ces opérations sont valables avec chacun des nombres définis précédemment et peuvent même être utilisées entre des nombres de types différents.
Voici quelques exemples d’utilisation :
>>> 2 + 3.5
5.5
>>> 3 * 6
18
>>> 3 - 7
-4
>>> 7 / 2
3.5
>>> 7 // 2
3
>>> 7 % 2
1
>>> (2+3j) + 0.1
(2.1+3j)
>>> -+-1
1
Le dernier exemple montre bien qu’il s’agit d’opérateur unaire (même si ce genre d’opérations est peu intéressant). L’opération modulo % est extrêmement utile pour déterminer si un nombre a est multiple d’un autre b : en effet, si a est un multiple de b, alors b divise exactement a et le reste est nul. Ainsi, l’expression a % b == 0 sera vraie :
>>> 9 % 3 == 0
True
car 9 est bien un multiple de 3.
Python sait reconnaitre un même nombre même s’il est admet plusieurs écritures différentes (donc même s’il est représenté par différents objets) :
>>> 1 == 1.0
True
>>> 1 == (1 + 0j)
True
Note
La coercition est une opération consistant à convertir de manière implicite le type d’un objet en un autre type lors d’une opération (grâce à l’usage implicite de la fonction built-in coerce et à la méthode spéciale de classe __coerce__), afin d’éviter au programmeur de devoir le faire manuellement. Ainsi, cela permet d’utiliser les opérations qui viennent d’être définies entre des nombres de types différents. Il s’agit d’une fonctionnalité dépréciée depuis Python 3, car elle contredisait l’idée du typage fort.
Ceci ne change pas réellement les habitudes du programmeur puisque, lors de la définition d’un nouveau type, il fallait et il faudra dans tous les cas prendre en charge explicitement les conversions.
Les conteneurs sont des objets qui en contiennent d’autres. Une sous-catégorie très importante des conteneurs est celle des séquences : dans ces dernières, les objets contenus sont ordonnées selon un certain ordre. Dans la suite de cette section, nous allons étudier quelques séquences (listes, tuples et chaines de caractères), ainsi que les dictionnaires et les ensembles.
Dans une séquence, un objet est repéré par un indice (“index” en anglais), qui vaut 0 pour le premier élément de la séquence, et n-1 pour le dernier (n étant la longueur de celle-ci).
Warning
Il convient de faire très attention au fait que la numérotation des index commence à 0. Il s’agit d’une particularité en programmation, très commune.
Dans le cas d’un conteneur non ordonné, l’accès aux éléments — lorsqu’il est possible — se fait à l’aide d’une clé (“key”) : il s’agit d’un identifiant, qui peut être n’importe quel objet non modifiable (une chaine, un nombre, un tuple...).
Les index et clés sont indiqués entre crochets immédiatement après l’objet.
sequence[key]
sequence[index]
L’accès avancé aux éléments, notamment avec le slicing, sera abordé plus loin.
Une liste est délimitée par des crochets, et chaque élément est séparé du précédent par une virgule. Il s’agit d’un conteneur dont les différents éléments peuvent être modifiés grâce à une simple affectation. L’ajout se fait avec la méthode append(). Finalement, la fonction list() permet de convertir un objet en liste.
>>> l = ['a', 'b', 'c']
>>> l
['a', 'b', 'c']
>>> type(l)
<class 'list'>
>>> l[0]
'a'
>>> l[2]
'c'
>>> l[4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> l[1] = 'e'
>>> l
['a', 'e', 'c']
>>> l.append('z')
>>> l
['a', 'e', 'c', 'z']
Les tuples sont construits à partir de plusieurs éléments séparés par des virgules ; le tout peut éventuellement être délimité par des parenthèses. Les tuples sont très semblables aux listes, mis à part qu’ils sont immuables : comme conséquence, ils consomment moins de mémoire et ils sont à privilégier si l’on sait que l’on ne souhaite pas modifier la séquence d’objets.
La fonction tuple() permet de convertir en tuple.
>>> t1 = 'a', 'b', 'c', 'd'
>>> t1
('a', 'b', 'c', 'd')
>>> t = ('a', 'b', 'c', 'd')
>>> t[1]
'b'
>>> t[1] = 'e'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Puisque les parenthèses servent aussi à délimiter les expressions, un tuple constitué d’un seul élément doit se déterminer par une virgule.
>>> (1)
1
>>> type((1))
<class 'int'>
>>> (1,)
(1,)
>>> type((1,))
<class 'tuple'>
Warning
Cette particularité est régulièrement source d’erreurs, et l’on oublie trop aisément la virgule finale.
Une chaine de caractères (“string”, abrégé en “str”), comme son nom l’indique, est un ensemble de lettres, que l’on utilisera pour des mots, des phrases, voire des textes entiers. Une chaine est représentée comme une séquence de caractères (chaine de longueur 1), même s’il n’existe aucune différence fondamentale entre ces deux concepts. La fonction de conversion est str().
Une chaine peut être délimitée par :
Les deux dernières notations permettent d’écrire une chaine sur plusieurs lignes.
Note
Contrairement à certains langages qui font la différence entre les apostrophes et les guillemets pour entourer, il n’y en a aucune en Python : les deux délimiteurs sont totalement équivalents.
Il s’agit d’un type immuable car il est impossible de changer individuellement l’un des caractères, mais il est possible d’accéder individuellement aux caractères par leurs indices.
>>> s = 'Hello'
>>> type(s)
<class 'str'>
>>> isinstance(s, str)
True
>>> s[1]
'e'
>>> s[1] = 'a'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> "string"
'string'
>>> print('''première ligne
... deuxième ligne''')
première ligne
deuxième ligne
Warning
Il est impossible d’utiliser le délimiteur choisi dans la chaine elle-même : il est nécessaire de l’échapper à l’aide de l’antislash \.
>>> 'l'enfant'
File "<stdin>", line 1
'l'enfant'
^
SyntaxError: invalid syntax
Pour éviter cette erreur, on peut soit choisir d’entourer la chaine avec des guillemets, soit faire précéder l’apostrophe intérieur d’un antislash :
>>> "l'enfant"
"l'enfant"
>>> 'l\'enfant'
"l'enfant"
Warning
Rappelons que Python est un langage au typage fort : il ne considère donc pas '1' comme un nombre :
>>> '1' + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly
Un dictionnaire est un conteneur, délimités par des accolades, dont les éléments sont accessibles par une clé. Les paires clé-valeur s’écrivent clé: valeur et sont séparés par des virgules. Enfin, il est possible de changer la valeur associée à une clé ou d’ajouter une nouvelle paire par une simple affectation. La fonction intégrée dédiée à la création de dictionnaires est dict().
>>> dic = {'a': 'val', 3: 'x', 'key': 124}
>>> type(dic)
<class 'dict'>
>>> dic['a']
'val'
>>> dic[3]
'x'
>>> dic['key'] = 9
>>> dic
{'a': 'val', 3: 'x', 'key': 9}
>>> dic['new'] = 'Hello'
>>> dic
{'a': 'val', 'new': 'Hello', 3: 'x', 'key': 9}
Remarquons que la nouvelle valeur se retrouve en deuxième position : les dictionnaires ne sont pas des ensembles ordonnés, c’est à dire que l’on ne peut prédire à quel endroit s’ajoutera une paire. Ils ne sont pas adaptés pour stocker des objets qui doivent restés classés quoiqu’il arrive (pour cela il convient de se tourner vers les listes ou les tuples).
Les ensembles (“set”) Python sont très similaires aux ensembles mathématiques : il s’agit d’un ensemble d’objets non ordonnés et uniques. On peut les créer soit en utilisant la fonction set() (remarquons qu’il existe la fonction frozenset() pour créer un ensemble immuable), soit en utilisant les accolades {} et en plaçant à l’intérieur la liste des objets :
Il ne faut pas confondre les dictionnaires et les ensembles, bien que la syntaxe utilise le même délimiteur.
Voici la liste des opérateurs spécifiques aux ensembles (d’autres opérations communes à tous les conteneurs seront présentées bientôt) et de leurs effets :
Ils ne trouvent que rarement une utilité, mais je vais ici donner un exemple, qui permettra de plus de mettre en applications les opérations. Imaginons que je souhaite collectionner les quinze cartes d’un certain jeu, (numérotées de 1 à 15) et que je ne m’intéresse pas à la quantité que je possède (un peu comme si on collait ces cartes dans un album). Dans ce cas les ensembles seront parfaitement adaptés pour représenter ma collection. Finalement je peux agrandir ma collection un achetant un blister de trois cartes :
>>> cards = set(range(1, 16))
>>> deck < cards
True
>>> cards - deck
{1, 2, 5, 6, 8, 10, 11, 13, 14, 15}
>>> blister = {2, 7, 11}
>>> deck | blister
{2, 3, 4, 7, 9, 11, 12}
>>> deck & blister
{7}
>>> blister - deck
{2, 11}
Nous avons d’abord vérifié que nos cartes était bien un sous-ensemble des de la collection, puis calculé successivement : les cartes qui nous manquent, les cartes que l’on a après avoir acheté le blister, les cartes en doublons puis les nouvelles cartes apportées par le blister. Les opérations peuvent bien sûr être enchainées :
>>> {1, 3} | {5} | {4, 10, 12}
{1, 3, 4, 5, 10, 12}
Le module collections propose différents types de conteneurs qui peuvent être utilisés lorsque l’on a des besoins précis. Par exemple, afin de répondre à une question qui se pose souvent, on peut construire des dictionnaires ordonnées à partir de la classe OrderedDict du module standard . Toutefois, une réflexion plus ou moins poussée permet de trouver une méthode plus élégante qu’un dictionnaire ordonné (par exemple en choisissant une liste de tuple à la place...). On y trouvera aussi un objet pour fabriquer des compteurs (Counter).
Notons enfin l’existence des types bytes et bytearray, qui permettent de représenter des chaines de caractères ASCII.
La fonction range() retourne un objet de ce type, qui représente une suite de nombres entiers.
De nombreuses opérations sont définies sur les conteneurs :
Nous avons dit que les tuples étaient immuables, or nous présentons ici des opérations entre eux : en fait, crée un nouveau tuple, dans lequel il place les éléments des deux tuples d’origine.
Warning
Les copies réalisées avec la “multiplication” sont superficielles, c’est à dire que seules les références sont copiées :
>>> l = [[]]
>>> l *= 3
>>> l[0].append(3)
>>> l
[[3], [3], [3]]
De plus, il existe quelques fonctions qui peuvent être utiles.
Retourne la longueur dans le conteneur s.
Retourne le plus petit élément dans le conteneur s.
Retourne le plus grand élément dans le conteneur s.
Note
Si s est un dictionnaire, alors toutes les opérations se font au niveau des clés.
Les séquences possèdent les deux méthodes suivantes :
Mettons un peu en pratique toutes ces opérations :
>>> d = {'a': 1, 'b': 5}
>>> 'a' in d
True
>>> 5 in d
False
>>> max(d)
'b'
>>> t = (1, 3) + (5, 6, 1)
>>> t
(1, 3, 5, 6, 1)
>>> 8 not in t
True
>>> len(t)
5
>>> t.count(1)
2
>>> t.index(3)
1
>>> min(t)
1
>>> [1, 2] * 4
[1, 2, 1, 2, 1, 2, 1, 2]
Le slicing permet d’extraire une sous-séquence d’une autre séquence (dans le cas où un seul élément est sélectionné, il est simplement retourné). La syntaxe générale est seq[i:j:k], où i, j et k sont des entiers, éventuellement négatifs : la sous-séquence sera composée de tous les éléments de l’indice i jusqu’à l’indice j-1, par pas de k (ce dernier nombre peut être omis).
Si i ou j sont négatifs, cela est équivalent à l’indice n-i et n-j, où n est la taille de la séquence (on a écrit explicitement le signe moins des variables i et j). Si k est négatif, alors l’ordre des éléments est inversé.
>>> l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[-3]
7
>>> l[7]
7
>>> l[2:5]
[2, 3, 4]
>>> l[-6:8]
[4, 5, 6, 7]
>>> l[-6:-2]
[4, 5, 6, 7]
>>> l[5:2:-1]
[5, 4, 3]
>>> l[2:9:3]
[2, 5, 8]
L’objet nul est None. Il correspond à la valeur NULL (PHP, C...), ou encore nil (Pascal) d’autres langages.
Dans la plupart des cas, il signifie l’absence de valeur. Il est par exemple utilisé comme valeur de retour par les fonctions qui ne retournent aucune valeur explicitement.
Toutes les classes (prédéfinies ou non) sont du type type :
>>> type(str)
<class 'type'>
Les méthodes et attributs d’une classe peuvent être explorées à l’aide de l’attribut __dict__ (toutefois cet attribut n’est pas fait pour être modifié, il vaut mieux utiliser les méthodes et attributs prévus à cet effet). Un chapitre ultérieur est dédié à la création et à l’utilisation des classes.
Note
type(), déjà utilisée de nombreuses fois, renvoie le constructeur de l’objet passé en argument. Ainsi, puisque le constructeur des types est justement la fonction type(), nous pouvons vérifier que l’objet retourné est la fonction type() elle-même. De même, le constructeur d’un nombre est la fonction int(), à laquelle on peut donner un argument qui est converti en int :
>>> type(int) is typetype(int) is type
True
>>> type(1)(3.4)
3
Les modules sont des objets contenant un ensemble d’autres objets définis dans un fichier. Ces objets sont créés à l’aide du mot clé import :
>>> import os
>>> type(os)
<class 'module'>
Là aussi nous pouvons accéder à l’ensemble des objets à l’aide de l’attribut __dict__, mais nous utiliserons toujours la notation pointée pour obtenir un objet :
>>> sys.platform
'linux2'
Le module sys contient des informations utiles concernant le système d’exploitation et la configuration de Python. L’attribut platform indique quel est le système d’exploitation (nous aurions eu win32 avec Windows).
Nous attendrons là aussi le chapitre dédié à ce sujet pour en parler plus en détails.
Dans ce chapitre, nous avons étudié de manière superficielle les principaux types du langage Python, ainsi que leurs opérations :
Les fonctions isinstance() et type() permettant d’étudier le type d’un objet ont aussi été abordées.
Choisir un nombre (par exemple 1) et divisez-le mille fois par 10. Multipliez-le ensuite mille fois par 10 (on pourra attendre d’avoir lu le chapitre sur les boucles). Expliquez le résultat.
Créer un système de permissions qui fonctionne comme suit :
- chaque permission est associée à un nombre (e.g. 1 : “peut écrire un article”, 2 : “peut éditer un article”...) ;
- chaque groupe se voit attribué plusieurs permissions ;
- un utilisateur peut appartenir à plusieurs groupes ;
- tester si un utilisateur a certaines permissions (on pourra attendre la lecture du prochain chapitre pour répondre à cette question, voire celle sur les fonctions afin de simplifier le code).
Footnotes
[1] | La notation des nombres à virgule diffère ici de celle habituellement adoptée en France. |
[2] | On a j² = -1. |
[3] | a est appelé partie réelle et b est appelé partie imaginaire. |
[4] | Les mathématiciens préfèrent utiliser la lettre i. |
[5] | Ainsi, les nombres négatifs ne sont pas des nombres en eux-mêmes, mais plutôt considéré comme les opposés des nombres positifs. |