Syntaxe Python de base


Le but de cet article est de présenter de façon succincte les éléments de base de la syntaxe Python. Pour un développeur C#, l’apprentissage de Python peut paraître aisé car la plupart des mots clé sont identiques toutefois comme souvent il faut éviter de penser par analogie. Python est un langage particulier avec ses caractéristiques qui peuvent être spécifiques par moment.

Ce premier article passe en revue les éléments de syntaxe de base en indiquant les différences marquantes avec un langage comme C#. D’autres articles permettront d’aborder d’autres aspects spécifiques de l’environnement Python.

Des indications sont apportées lorsque des éléments de syntaxe sont très différents des autres langages.

Sommaire

Python en quelques mots
Versions
Syntaxe positionnelle

Variable et typage
  type()
  Mutable vs immutable
Types courants
  Booléens
  None
  Entier
  Flottant
  Chaîne de caractères
  Bytes
Portée des variables
  global
  globals() et locals()
  id()

Les conditions
if…then…else
  elif
  Version condensée
  bool()
Opérateurs booléens

Les collections
Liste
  list()
  Index
  Affectation de plusieurs éléments (list slicing)
  Passage par référence
  Ajouter des éléments
  Supprimer un élément
  Effectuer une copie d’une liste
  len()
  count()
  Concaténer des listes
  Répéter le contenu d’une liste (avec *)
  in
  Liste de listes
  Inverser l’ordre des éléments
  Ordonner les éléments de la liste
  Déconstruction
Tuples
  tuple()
  len()
  count()
  Concaténation d’un tuple (avec +)
  Répéter le contenu d’un tuple (avec *)
  in/not in
  Tuple de tuples
  zip()
  Déconstruction
Dictionnaire
  Modifier une valeur
  update()
  Supprimer une clé/valeur
  get()
  dict()
  keys()
  values()
  Parcourir les valeurs d’un dictionnaire
  copy()
  Dictionnaires imbriqués
  in/not in
set
  set()
  add()
  update()
  Supprimer un élément d’un set
  Effectuer une copie d’un set
  in/not in
  Opérations applicables sur les sets
Itérable

Fonctions
Arguments
  Paramètre par défaut
  Préciser le nom des arguments
  Nombre variable d’arguments
  Arguments variables indiqués sous forme d’un dictionnaire
Fonctions imbriquées
Fonctions de premier ordre
Quelques fonctions particulières
  map()
  filter()
  reduce()
Fonction lambda

Boucles
for
  range()
while
break et continue
Enumérateur
Comprehensions
  List comprehension
  Sets comprehension
  Dictionary comprehension
Generators
  Fonctions generator avec état
  Generator comprehension (ou generator expression)

Exceptions
Gestion de plusieurs types d’erreurs
Prendre en compte tous les types d’exceptions
  Pour afficher l’erreur
Relancer une exception
  Lancer une exception
finally
else

Classe
Instancier une classe
Méthode membre
Initializer
Attributs de classe et d’instance
Définir une variable statique
Héritage et polymorphisme
  Dériver d’une classe
  Surcharger une fonction
  Héritage multiple

Lecture et écriture de fichiers
Ecrire un fichier
Lire un fichier
  Utiliser des iterators
Ecrire à la suite d’un fichier texte
Ecrire un fichier binaire
  Utiliser un bloc try…finally
  Considérer un contexte de lecture avec des “with blocks”

Python en quelques mots

Python est un langage interprété multiplateforme libre permettant la programmation de haut niveau impérative, fonctionnelle et orientée objet. La gestion de la mémoire est automatique. Une caractéristique importante de ce langage est que les éléments techniques de programmation et de syntaxe sont simplifiés pour faciliter son implémentation. D’autre part, il dispose d’une grande richesse de bibliothèques techniques et scientifiques. La syntaxe est positionnelle c’est-à-dire qu’il n’y a pas d’accolades. Enfin, l’implémentation dans ce langage est extensible en C.

Parmi ses défauts, on peut citer sa lenteur par rapport à des langages compilés. Bien-que le typage est fort, il est moins stricte car dynamique. Ensuite, il n’y a pas de pointeurs, il n’est donc pas possible d’effectuer des manipulations de la mémoire. Enfin, le code ne permet pas d’effectuer de l’encapsulation.

Versions

Date Python 2 Python 3
Octobre 2000 2.0
  • Prise en charge des chaînes de caractères Unicode
  • List comprehension
  • Algorithme de Garbage Collection se basant sur des cycles plutôt que sur un compteur.
Avril 2001 2.1 Portée imbriquées des variables.
Décembre 2001 2.2
  • Unification de la hiérarchie orientée objet des types et classes.
  • Ajout des generators.
Juillet 2003 2.3
Novembre 2004 2.4
  • Ajout generator expression (generator comprehension)
  • Décorateur de fonction
  • Type decimal
Septembre 2006 2.5 Ajout de with
Octobre 2008 2.6 Ajout des fonctionnalités de la 3.0 dont typeError, bin(), _complex_()
Décembre 2008 3.0
  • Déplacement de la fonction reduce() dans functools.
  • Modification des exceptions avec l’utilisation du mot-clé as.
  • Ajout de with.
  • Amélioration de la syntaxe pour la fonction print().
  • raw_input a été renommé en input.
  • Les chaînes de caractères sont en Unicode.
  • La division renvoie un float plutôt qu’un entier. Il faut utiliser // pour avoir un entier.
Juin 2009 3.1 L’ordre de parcours des dictionnaires est conservé.
Juillet 2010 2.7 Bug fix
Février 2011 3.2 Ajout du module argparse
et futures.
Septembre 2012 3.3
  • Ajout de yield from.
  • Ajout de la possibilité de déclarer une chaîne unicode pour faciliter la transition python 2 vers python 3.
  • Il n’est plus nécessaire d’indiquer le type précis d’une exception pour qu’elle soit attrapée avec try...except, on peut utiliser des erreurs plus génériques comme OSError.
Mars 2014 3.4 Ajout du module asyncio.
Septembre 2015 3.5 Support de l’implémentation asynchrone avec des objets awaitables, coroutine, itération asynchrone, gestionnaire de contexte asynchrone.
Décembre 2016 3.6
  • Support des generators asynchrones, comprehensions asynchrones.
  • Ajout des f-strings.
Juin 2018 3.7
  • Ajout des mots clés async/await.
  • Evaluation des annotations durant l’exécution.
  • Ajout de la fonction breakpoint().
Octobre 2019 3.8
  • Ajout de l’opérateur := (walrus operator) permettant d’assigner une variable dans une expression.
  • Arguments de fonction positionels seulement.
  • Ajout de l’opérateur dans les f-strings pour représenter une expression et le résultat de l’évaluation de cette expression.
Octobre 2020 3.9
  • Ajout des opérateur | pour merger 2 dictionnaires et |= pour merger 2 dictionnaires et mettre à jour un des dictionnaires.
  • Ajout des fonctions str.removeprefix() et str.removesuffix() pour supprimer certaines parties d’une chaîne de caractères.
Octobre 2021 3.10
  • Amélioration de la recherche d’erreurs.
  • Pattern matching structurel avec switch...case.
  • Opérateur | pour indiquer l’union de 2 types dans la définition d’arguments de fonctions.
  • Ajout des fonctions aiter() et anext() pour des itérations asynchrones.

Syntaxe positionnelle

Il n’y a pas d’accolades ni de points virgules pour délimiter les instructions. En revanche, les espaces et retours à la lignes sont significatifs:

  • L’indentation permet de délimiter les blocs de code. Généralement, 4 espaces sont utilisés.
  • Il ne faut pas mélanger les tabulations et les espaces.

Par exemple:

for i in range(10):
    # 4 espaces pour indiquer un bloc
    if i % 2 == 0:
        # 4 espaces de plus pour indiquer un autre bloc
        print('Pair %d' % i)
    else:
        print('Impair %d' % i)

Il est conseillé d’utiliser:

  • 1 saut de ligne pour délimiter du code dans un même bloc et
  • 2 sauts de lignes pour différencier des blocs différents: par exemple entre 2 fonctions et 2 classes etc…

Ces préconisations ne sont pas obligatoires mais fortement conseillées. Ne pas les suivre peut entraîner des warnings de certains IDE.

Variable et typage

En Python, le typage des variables est dynamique et fort, cela signifie que:

  • Une affectation permet de déclarer, d’initialiser une variable et de typer une variable: la valeur d’initialisation permet d’indiquer le type, il n’y a pas de mot clé pour indiquer le type.
  • On peut changer le type de certaines variables en effectuant une nouvelle affectation. Suivant le type initial, le changer par une nouvelle affectation n’est pas tout le temps possible.
  • Une variable typée a des caractéristiques spécifiques à son type. Une erreur est levée si des opérations non conformes à ce type sont effectuées.
  • Une erreur est levée si une variable est utilisée sans être initialisée.

Par exemple:

>>> a = 10 # la variable a est déclarée et initialisée en tant qu'entier.
>>> b = a + '10'  # ERREUR car '10' est une chaine de caractères.

>>> print(c)      # ERREUR car c n'a pas été initialisée
>>> a = 'chaine'  # OK a est désormais une chaine de caractères.

Il n’existe pas de mot clé comme var ou let pour indiquer qu’on déclare une variable, seule l’initialisation permet la déclaration d’une variable locale en dehors des arguments d’une fonction.

type()

Cette fonction retourne le type d’une variable, par exemple:

>>> a = 'Ceci est une chaine'
>>> print(type(a))
<class 'str'>

>>> a = 5
>>> print(type(a))
<class 'int'>

>>> a = 5.0
>>> print(type(a))
<class 'float'>

Mutable vs immutable

Suivant son type, il sera possible de modifier ou non la valeur d’une variable:

  • Mutable: on peut modifier la valeur d’une variable,
  • Immutable: après initialisation, toute modification de la valeur d’une variable ne sera pas possible sans effectuer une nouvelle affectation.

Par exemple, une chaîne de caractères est immutable:

a = 'ABCDEF'
a[1] = 'Z'    # ERREUR: on ne peut pas modifier une chaîne de caractères
a = 'FEDCBA'  # OK nouvelle affectation = nouvelle instance

On indiquera par la suite si le type est mutable ou immutable.
Parmi les types de base, tous les types sont immutables sauf les collections. Les collections sont mutables à l’exception des tuples qui sont immutables.

Types courants

On va passer en revue les types courants et leurs caractéristiques:

Booléens

Ce type est immutable, les valeurs possibles sont True ou False.

Les opérateurs logiques sont: and, or et not:

>>> a = True
>>> b = False
>>> print(a and b)
False

>>> print(a and not b)
True

bool()

Cette fonction renvoie un booléen correspondant à la valeur en argument. Contrairement à ce qu’on pourrait croire, cette fonction n’effectue pas de cast, le booléen en retour dépend du type de l’argument et de sa valeur (voir Truthy vs Falsy).

>>> a = 'True'  # Ceci est une chaîne de caractères
>>> b = bool(a)
>>> print(a)
True

>>> print(type(b))
<class 'bool'>
Truthy vs Falsy

Le comportement de bool() n’est pas forcément celui auquel on s’attend. Cette fonction n’effectue pas un cast, elle évalue l’objet fourni en argument pour renvoyer un booléen.

Par exemple:

>>> print(bool('True'))
True

>>> print(bool('False'))
True

bool() renvoie vrai car la chaîne de caractère est non vide.

Ainsi, certaines valeurs peuvent être:

  • Falsy quand une évaluation avec bool() renvoie False et
  • Truthy quand une évaluation avec bool() renvoie True.

Les valeurs Falsy sont:

  • Collections:
    • Structure vide (liste, tuple, dictionnaire, set)
    • Chaine de caractères vide
    • range(0)
  • Nombres: nombre égal à 0
    • entier: 0
    • flottant: 0.0
    • Nombre complexe: 0j
  • Constantes:
    • None
    • False

Les valeurs Truthy sont:

  • Liste non vide
  • Nombre différent de 0
  • True

Par exemple:

bool(0) == False
bool(0.0) == False
bool(0.2) == True
bool([]) == False        # car la liste est vide
bool([5, 9, 6]) == True  # car la liste est non vide
bool("") == False        # car la chaîne est vide
bool("Span") == True     # car la chaîne est non vide
bool("True") == bool("False") == True  # car non vide

Ne pas utiliser bool() pour déterminer si une variable est initialisée.

Si on utilise bool() pour déterminer si une variable est initilisée, il se produira une erreur car la variable n’a pas été déclarée:

if bool(unknown_value):
    print('OK')
else:
    print('KO')

ERREUR: NameError: name 'unknown_value' is not defined

Il n’y a pas de moyen simple de voir si une variable est initialisée, il faut l’entourer d’un try...except:

try:
    if unknown_value:
        print('OK')
    else:
        print('KO')
    except NameError:
        print('KO')

None

None est une constante qui pourrait correspondre à null dans les autres langages. Il s’agit d’un type et d’une valeur, on peut utiliser == ou is pour comparer une variable à None:

>>> a = None # affectation de la valeur None
>>> print(a)
None

>>> print(type(a))
<class 'NoneType'>

>>> print(a == None)
True

>>> print(a is None)
True
Une variable non initialisée n’a pas pour valeur None

Même si None est un équivalent de null pour d’autres langages, une variable non initialisée n’a pas pour valeur None. Comme indiqué précédement, une variable non initialisée en Python n’est pas non plus déclarée. Si a n’a pas été initialisée, la ligne suivante mène à une erreur:

if a == None:
    print(OK)  # ERREUR car 'a' n'a pas été initialisée

Entier

Un entier est immutable. Contrairement aux autres langages, Python utilise un nombre de bits pour stocker des entiers. La taille de l’entier à stocker n’est pas donc limitée à la taille du type entier.

Par exemple:

from sys import getsizeof

a = 223423435364675675675676575675674324234234234234242343
print(getsizeof(a))  # 48 bytes

Pour affecter un entier sous forme décimal, il ne faut pas utiliser de point '.':

>>> a = 10
>>> print(type(a))

<class 'int'>

On peut affecter des entiers sous des formes différentes, par exemple:

  • binaire: 0b10
  • octodecimal: 0o10
  • Hexadecimal: 0x10

Par exemple:

>>> a = 0b10
>>> print(a)
2

>>> a = 0o10
>>> print(a)
8

>>> a = 0x10
>>> print(a)
16

bin(), oct() et hex()

On peut utiliser les fonctions bin(), oct(), hex() pour convertir respectivement en entier binaire, octodecimal ou hexadecimal, par exemple:

>>> a = bin(512)
>>> print(a)
0b1000000000

>>> a = oct(512)
>>> print(a)
0o1000

>>> a = hex(512)
>>> print(a)
0x200

int()

La fonction int() peut être utilisée pour convertir un objet en entier décimal quand cela est possible. L’objet à convertir peut être une chaîne de caractères ou un entier dans une base différente, par exemple:

>>> a = int('657')
>>> print(a)
657

>>> print(type(a))
<class 'int'>

>>> b = int(0x2ED0)
>>> print(b)
11984

Si la conversion n’est pas possible, une erreur de type ValueError est renvoyée:

>>> int('dfgdfg')
ValueError: invalid literal for int() with base 10: 'dfgdfg'

Flottant

Les flottants sont immutables. Ce type permet de stocker les nombres flottants. Ils doivent être initialisés avec le caractère '.':

>>> a = 43.45
>>> print(a)
43.45

>>> print(type(a))
<class 'float'>

>>> b = 4.0

On peut utiliser la notation avec l’exposant:

>>> c = 1e+6
>>> print(c)
1000000.0

>>> d = 1e-3
>>> print(d)
0.001

float()

Cette fonction permet d’effectuer des conversions en nombre flottant quand cela est possible. L’objet à convertir peut être une chaîne de caractères:

>>> a = float('1.5e+4')
>>> print(a)
15000.0

>>> b = float('6565.989')
>>> print(b)
6565.989

>>> c = float('65,826')
>>> print(c)
ERREUR: ValueError: could not convert string to float: '65,826'

Opérateurs

On peut utiliser les opérateurs suivants:

  • +, -, /, * pour respectivement l’addition, soustraction, division et multiplication.
  • // division entière
  • ** puissance
  • % reste de la division
  • ? ET bit à bit
  • | OU bit à bit
  • ^ OU exclusif bit à bit

Conversion implicite

Python permet d’effectuer des conversions implicites pour des variables de type nombre comme float et integer.

Par exemple:

>>> a = 5
>>> print(type(a))
<class 'int'>

>>> b = a + 1.3
>>> print(b)    # conversion implicite: b est un flottant
6.3

>>> print(type(b))
<class 'float'>

>>> c = a + '1.3'  # conversion implicite non possible
ERREUR: TypeError: unsupported operand type(s) for +: 'int' and 'str'

>>> c = a + float('1.3')    # conversion explicite
>>> print(type(b))
<class 'float'>

>>> a = 7
>>> print(type(a))
<class 'int'>

>>> d = a/2
>>> print(d)
3.5        # A partir de Python 3, le résultat est de type float

>>> print(type(d))   # conversion implicite
<class 'float'>

‘nan’ et ‘inf’

'nan' (pour Not A Number) et 'inf' (pour infini) sont des flottants constants accessibles sous forme des chaînes de caractères. Par exemple:

>>> a = float('inf')
>>> print(a)
inf

>>> print(type(a))
<class 'float'>

>>> b = a /2
>>> print(b)
inf

>>> c = a + 6
>>> print(c)
inf

>>> d = inf
ERREUR: NameError: name 'inf' is not defined

>>> e = float('nan')
>>> print(e)
nan

>>> print(type(e))
<class 'float'>

>>> f = e /2
>>> print(f)
nan

>>> g = nan
ERREUR: NameError: name 'nan' is not defined

format()

La fonction format() avec une chaine de caractères permet de formater les nombres d’une certaine façon. Voir cette fonction dans le cadre des chaines de caractères.

Chaîne de caractères

Une chaîne de caractères est immutable. Il n’existe pas caractères en Python, un caractère est stocké sous forme d’une chaîne de caractères.

Les chaines sont en Unicode (UTF-8).

Ainsi:

>>> a = 'ABCDEF'
>>> a[2] = 'A'
ERREUR TypeError: 'str' object does not support item assignment
# Car les chaînes de caractères sont immutables

Quand on utilise a[2], on obtient le 3e caractère de la chaine mais le résultat est de type string.

Pour définir des chaînes de caractères, on peut utiliser '...' ou "...". Si on doit effectuer des commentaires sur plusieurs lignes, il faut utiliser """...""":

>>> a = 'ABCDEF'
>>> b = "ABCDEF"
>>> c = """ABCDEF"""
>>> print(a == b)
True

>>> print(a == c)
True

>>> d = """Une
chaîne
sur
plusieurs
lignes""" 

>>> print(d)

Quand on doit inclure un caractère quote ' dans une chaîne, on peut utiliser "..." pour délimiter la chaîne:

example = "This's is a string with a quote"

Inversement si on doit inclure des caractères " dans une chaîne, on peut utiliser ' pour délimiter la chaîne:

example = '"Yes" or "No"'

On peut aussi utiliser la caractère d’échappement \, par exemple:

'This is a \' character'     # ' est échappé
"This is a \" character"     # " est échappé
"This is a \\ character"     # \ est échappé

Enfin on peut déclarer la chaîne avec le préfixe r pour raw (voir plus bas).

Le type char n’existe pas en Python

Un caractère est indiqué sous la forme d’une chaîne de caractères contenant un seul caractères.

city = "Oslo"

city[2] est une chaîne de caractères.

Chaînes de caractères sur plusieurs lignes

Généralement """ est utilisé pour les commentaires de fonctions. On peut utiliser """ pour des commentaires sur plusieurs lignes. Si la chaine n’est pas utilisée pour effectuer une affectation alors elle sera considérée comme un commentaire.

Préfixes pour les chaines de caractères

  • u – unicode: par défaut les chaines de caractères en Python sont en UTF-8. Ce préfixe n’est pas nécessaire toutefois il existe pour apporter une compatibilité avec Python 2.
  • b – byte: les variables initialisées de cette façon b'...' semblent être des chaines de caractères toutefois ce n’est pas le cas. Il s’agit d’un tableau de bytes (octet) dont chaque caractère ASCII correspond à un entier codé entre 0 et 255.

    Par exemple:

    a = b'Not a string'
    

    a[4] retourne 97. a[4] correspond au caractère 'a' dont l’encodage ASCII est 97.

    Si on tente d’utiliser un caractère ne faisant pas partie de l’encodage ASCII, on obtient une erreur:

    a = b'Not a string àé'
    

    On obtient une erreur:

    SyntaxError: bytes can only contain ASCII literal characters.
    
  • r – raw: ce préfixe est utilisé pour indiquer que la chaine de caractères doit être traitée de façon brute.

    Par exemple:
    Le caractère \n est interprété comme un retour à la ligne:

    >>> print('Retour\nà\nla\nligne')
    Retour
    à
    la
    ligne
    

    Si on utilise le préfixe r, \n n’est pas interprété comme un retour à la ligne:

    >>> print(r'Retour\nà\nla\nligne')
    Retour\nà\nla\nligne
    

    L’utilisation du caractère d’échappement \ produit le même résultat:

    >>> print('Retour\\nà\\nla\\nligne')
    Retour\nà\nla\nligne
    
  • f – formatting: permet de formater les chaines en exécutant ce qui se trouve entre les caractères { }, par exemple:
    >>> a = 4
    >>> b = 'quatre'
    >>> c = f'Le chiffre {a} en lettres est {b}'
    

    On obtient:

    Le chiffre 4 en lettres est quatre
    

On peut combiner les préfixes et ils ne sont pas sensibles à la casse.

str()

Cette fonction permet de convertir en chaîne de caractères des objets ayant un autre type, par exemple:

>>> a = str(6.02)
>>> b = str('466')
>>> print(a)
6.02

>>> print(type(a))
<class 'str'>

>>> print(b)
466

>>> print(type(b))
<class 'str'>

len()

Cette fonction permet de retourner la longueur d’une chaîne de caractères:

>>> a = 'Example string'
>>> len(a)
14

join()

Concaténer des chaines de caractères avec un caractère:

";".join(['str1', 'str2', 'str3'])

On obtient: ‘str1;str2;str3’

On peut aussi faire cette manip avec une chaine vide:

''.join(['str1', 'str2', 'str3'])

On obtient: 'str1str2str3'.

split()

colors.split(';')

On obtient ['str1', 'str2', 'str3'].

Partitionner des chaines

On peut partitionner une chaine en utilisant une autre chaine en tant que séparateur:

unforgetable = 'unforgetable'
unforgetable.partition("forget") 

"forget" est la chaine de séparation.

On obtient: ('un', 'forget', 'able')

On peut utiliser le caractère _ (ie. underscore) pour indiquer qu’une variable n’est pas utilisée:

origin, _, destination = "Seatle_Boston".partition('_')      

C’est une espèce de déconstruction.

On obtient:

origin == 'Seatle'
destination == 'Boston'

format()

La fonction format() permet de positionner des chaînes de caractères dans une autre chaîne en utilisant des arguments avec '{...}'.

Il existe plusieurs syntaxe pour cette fonction, certaines syntaxes sont anciennes et d’autres plus actuelles. Dans cette partie ne seront présentée que les fonctionnalités principales de cette fonction, pour avoir une liste exhaustive de ces fonctionnalités se reporter à la page https://pyformat.info/.

Une 1ère syntaxe permet de nommer les variables, par exemple:

>>> example = 'Ma position est: {latitude} {longitude}'
>>> print(example.format(latitude='60N', longitude='5E'))
Ma position est: 60N 5E

On peut placer une chaîne suivant son index dans la liste des arguments de la fonction format(), par exemple:

>>> example = 'Ma position est: {1} {0}'
>>> print(example.format('5E', '60N'))   # '5E' est à l'index 0; '60N' est à l'index 1
Ma position est: 60N 5E

Une autre syntaxe permet d’utiliser un motif pour indiquer l’emplacement de la chaîne à placer:

  • '%s' pour placer une chaîne de caractères
  • '%d' pour placer un entier
  • '%f' pour placer un flottant
  • '{}' permet de placer n’importe quel type d’objet

Pour plus de détails dans le cas des nombres:

  • Entiers:
    • Dans une chaine:
      • '%d' % (42,)42
      • '{:d}'.format(42)42
    • Padding:
      • '%4d' % (42,)' 42'
      • '{:4d}'.format(42)' 42'
      • '%04d' % (42,)'0042'
      • '{:04d}'.format(42) '0042'
    • Avec des nombres signés:
      • '%+d' % (42,)'+42'
      • '{:+d}'.format(42)'+42'
      • '% d' % ((- 23),)' -23'
      • '{: d}'.format((- 23))'-23'
      • '% d' % (42,)' 42'
      • '{: d}'.format(42)' 42'
  • Float:
    • Dans une chaine:
      • '%f' % (7.345353465345345,)7.345353
      • '{:f}'.format(7.345353465345345)7.345353
    • Padding:
      • '%06.2f' % (7.345353465345345,)007.34
      • '{:06.2f}'.format(7.345353465345345) 007.34
        6 chiffres significatifs et 2 chiffres après la virgule.

Formattage avec %

Mise à part format(), une autre syntaxe permet de positionner une chaine en utilisant %, par exemple:

>>> longitude = '60N'
>>> latitude = '5E'
>>> print('Ma position est: longitude= %s  latitude= %s' % (longitude, latitude))
Ma position est: longitude= 60N  latitude= 5E

Dans le cadre de cet exemple, (longitude, latitude) est un tuple fourni à la chaine de caractères avec l’opérateur %. Ainsi si le tuple contient 2 éléments alors la chaine de caractères doit contenir 2 fois %s.

Ainsi pour d’autres types:

  • Pour un entier:
    >>> data = 5
    >>> print("Elément affiché: %s" % data)
    "Elément affiché: 5"
    
  • Dans le cas d’une liste:
    >>> data = [1, 2, 3]
    >>> print("Elément affiché: %s" % data)
    "Elément affiché: [1, 2, 3]"
    
  • Dans le cas d’un tuple:
    >>> data = (1, 2 ,3)
    >>> print("Elément affiché: %s" % data)
    ERREUR
    

    Pour afficher le tuple il faut écrire:

    >>> print("Elément affiché: %s" % (data,))
    "Elément affiché: (1, 2, 3)"
    

    Par contre:

    >>> data = (1, 2 ,3)
    >>> print("Eléments affichés: %s, %s, %s" % data)
    "Eléments affichés: 1, 2, 3"    # OK
    

Concaténer des chaînes de caractères (avec +)

L’opérateur + avec des objets de type string permet de concaténer des chaînes:

>>> concatanated_string = "Une" + " " + "chaine"
>>> print(concatanated_string)
Une chaine

Dupliquer le contenu d’une chaîne (avec *)

L’opérateur * permet de dupliquer le contenu d’une chaîne de caractères.

Par exemple:

>>> print('ABC' * 3)
ABCABCABC

Indexation

On peut récupérer un caractère si on applique un index sur une chaîne de caractères:

>>> example = 'ABCDEFG'
>>> print(example[2])
C

Il est possible d’appliquer d’autres arguments dans l’index:

 <chaîne de caractères>[<index debut>:<index fin exclu>:<pas>]

Tous les arguments de l’index ne sont pas obligatoires:

  • Si l’index de début n’est pas indiqué (par exemple [:3]) alors on considère toute la chaîne jusqu’à l’index de fin exclu.
  • Si l’index de fin n’est pas indiqué (par exemple [3:]) alors on considère la chaîne à partir de l’index de début jusqu’à la fin.
  • L’argument correspondant au pas est facultatif.

Par exemple:

  • Dans cet exemple, il s’agit d’une chaîne de caractères même dans le cas d’un seul caractère:
    >>> example = 'ABCDEFGHIJKLMNOP'
    >>> example[0]
    A
    
  • On considère la chaine à partir du 5e caractère jusqu’au 7e (le 8e étant exclu):
    >>> example[5:8]
    'FGH'
    
  • On commence au caractère à l’index 1 jusqu’au 7e (le 8e étant exclu) avec un saut d’un caractère (2e caractère après le caractère courant):
    >>> example[1:8:2]
    'BDFH'
    
  • Index négatif, par exemple 1 caractère en partant de la fin:
    >>> example[-1]
    'P'  
    
  • 3e caractère en partant de la fin jusqu’à 1 caractère exclu:
    >>> example[-3:-1]
    'NO'
    
  • Pour obtenir la chaîne de l’index 1 jusqu’à la fin en sautant 1 caractère:
    >>> example[1::2]
    'BDFHJLNP'
    

Partitionner des chaînes

On peut partitionner une chaîne en utilisant une autre chaîne en tant que séparateur:

>>> example = 'ABCDEFGHIJKLMNOP'
>>> example.partition('GHI')    # 'forget' est la chaine de séparation
('ABCDEF', 'GHI', 'JKLMNOP')

Le résultat est un tuple.

On peut effectuer une déconstruction avec le tuple et utiliser '_' (ie. underscore) pour ignorer une valeur, par exemple:

>>> example = 'ABCDEFGHIJKLMNOP'
>>> a, _, b = example.partition('GHI')
>>> print(a)
ABCDEF

>>> print(b)
JKLMNOP

Quelques autres fonctions

Les autres fonctions intéressantes pour chaînes sont:

  • capitalize() pour mettre la 1ère lettre de la chaîne en majuscule, par exemple:
    >>> example = 'hello'
    >>> example.capitalize()
    'Hello'
    
  • replace() pour remplacer une chaîne par une autre, par exemple:
    >>> example = 'hello hello hello'
    >>> example.replace('he', 'a')
    'allo allo allo'
    
  • isalpha() renvoie True si la chaîne contient seulement des caractères alphabétiques, par exemple:
    >>> example = 'hello'
    >>> example.isalpha()
    True
    
  • isdigit() renvoie True si la chaîne contient seulement des caractères numériques, par exemple:
    >>> example = '1234'
    >>> example.isdigit()
    True
    

Bytes

Le type bytes correspond à une suite d’octets. La valeur de cette suite peut être représentée sous la forme d’une chaîne de caractères en UTF-8 par défaut. On peut définir une suite de bytes en préfixant une chaîne de caractères avec b'...':

example = b'AbCdE123456789'

Cette écriture permet de définir une suite de bytes en convertissant chaque caractère encodé en UTF-8.

Il ne s’agit pas d’une chaîne de caractères mais bien d’une suite de bytes:

>>> type(example)
bytes

On peut passer d’une suite de bytes vers une chaîne de caractères et inversement en utilisant les fonctions encode()/decode():

  • encode(): pour passer d’une chaîne de caractères vers une suite de bytes:
    >>> string_object = 'ABCDEF'
    >>> bytes_object = string_object.encode()
    >>> print(bytes_object)
    b'ABCDEF'
    
  • decode(): pour passer d’une suite de bytes vers une chaîne de caractères:
    >>> bytes_object = b'ABCDEF'
    >>> string_object = bytes_objet.decode()
    >>> print(string_object)
    

L’encodage par défaut est UTF-8, on peut donc utiliser des caractères spéciaux:

>>> string_object = 'Caractères spéciaux ©'
>>> bytes_object = string_object.encode()
>>> print(bytes_object)
b'Caract\xc3\xa8res sp\xc3\xa9ciaux \xc2\xa9'

Si on tente d’encoder en ASCII les caractères spéciaux ne pourront pas être encodés:

>>> bytes_object = string_object.encode('ascii')
>>> print(bytes_object)
ERREUR

On peut ajouter des options à la fonction encode() pour gérer les caractères qui ne peuvent pas être encodés:

  • 'backslashreplace': utilise le caractère antislash pour les caractères qui ne peuvent pas être encodé.
  • 'ignore' ignore les caractères ne pouvant pas être encodés.
  • 'namereplace' remplace le caractère ne pouvant pas être encodés avec le nom du caractère.
  • 'strict' correspond à la valeur par défaut, une erreur est levée quand l’encodage n’est pas possible.
  • 'replace' remplace les caractères ne pouvant être encodés avec ?.
  • 'xmlcharrefreplace' remplace les caractères non encodables avec le caractère XML correspond.

Par exemple:

>>> string_object = 'Caractères spéciaux ©'
>>> bytes_object = string_object.encode(encoding='ascii', errors='xmlcharrefreplace')
# ou bytes_object = string_object.encode('ascii', 'xmlcharrefreplace')
>>> print(bytes_object)
b'Caractères spéciaux ©'

Portée des variables

La portée des variables est classique c’est-à-dire:

  • La portée d’une variable est locale au bloc dans lequel elle est déclarée et dans ses sous-blocs éventuels.
  • Les boucles et les clauses conditionnelles sont considérées comme des blocs.
  • Une variable est visible dans un bloc courant et dans les sous-blocs mais pas dans les blocs supérieurs.
  • Il est possible d’accéder à une variable d’un bloc supérieur mais pas aux variables de blocs de même niveau ou de niveau inférieur.
  • Une variable est globale lorsqu’elle est déclarée au niveau d’un script Python.
  • Une variable est locale lorsqu’elle est déclarée au niveau d’une classe ou d’une fonction.

Par exemple:

def define_a():
    a = 5
    print('Local a: %s' % a)

def print_a():
    print(a)
    define_a()

print_a()
ERREUR car a n'est pas déclaré à ce niveau

En revanche:

a = 10
define_a()
print_a()
Local a: 5    # a déclarée dans devine_a() est locale
10            # a déclarée à l'extérieur est globale

global

On peut utiliser ce mot-clé pour indiquer qu’on souhaite manipuler une variable globale, par exemple:

def define_a():
    global a
    a = 5
    print('Local a: %s' % a)

def print_a():
    print(a)
    a  = 10

define_a()
print_a()
Local a: 5    # a est modifiée au niveau global
5

globals() et locals()

globals() et locals() permettent de modifier la valeur de variables. Elles retournent un dictionnaire contenant toutes les variables rangées par nom. On peut directement modifier la valeur en intervenant sur le dictionnaire renvoyé.

Ainsi:

  • globals(): permet d’accéder aux objets globaux du bloc courant.

    Par exemple:

    def define_a():
        globals()['a'] = 5
        print('Local a: %s' % a)
    
    def print_a():
        print(a)
        a  = 10
    
    define_a()
    print_a()
    
    Local a: 5
    5
    
  • locals(): permet d’accéder aux objets locaux du bloc courant.

id()

Permet de renvoyer l’identifiant d’une variable:

id(<variable>)

La copie de valeur se fait par référence:

>>> a = 3
>>> id(a)
10935552

>>> b = a
>>> id(b)
10935552

>>> a = 5
>>> id(a)
10935616  # Nouvelle référence

>>> id(b)
10935552

Les conditions

Les opérateurs de comparaison en Python sont:

  • == pour évaluer l’égalité. Il s’applique aux nombres et aux chaînes de caractères.
  • != pour évaluer une inégalité. Cette opérateur s’applique aussi aux nombres et aux chaînes de caractères.
  • Les comparaisons avec <, <=,> et >=.

    Dans le cas de chaînes de caractères, ces opérateurs peuvent aussi être utilisés toutefois ils effectuent une comparaison des valeurs Unicode des caractères de la chaine en commençant par le premier index jusqu’au dernier. Ainsi:

    • '4' > '31' renvoie True car la valeur Unicode de '4' est supérieure à '3'.
    • '212' < '31' renvoie True.
    • La comparaison de chaînes de caractères peut mener à des erreurs si les évaluations se font avec le mauvais type:
      >>> value1 = '4'
      >>> value2 = '32'
      >>> value1 < value2
      False
      
    • La comparaison entre un nombre et une chaîne peut aussi mener à des erreurs en Python 2 car un nombre est toujours plus petit qu’un chaîne de caractères. En Python 3, une exception est levée.
  • is et is not permettent d’évaluer si 2 objets sont les mêmes ou non. Ainsi si on considère les listes suivantes:
    >>> list1 = [1, 2, 3]
    >>> list2 = [1, 2, 3]
    >>> list1 is list2
    False    # car les objets sont différents
    
    >>> list1 is list1
    True
    

    Mais:

    >>> list1 == list2
    True
    

    Avec les chaînes de caractères:

    >>> str1 = 'content1'
    >>> str2 = 'content1'
    >>> str1 is str2
    True     # car str1 et str2 sont le même objet.
    

    En revanche:

    >>> str3 = 'content'
    >>> str3 += '1'
    >>> str1 is str3
    False
    
Application des opérateurs avec None

Comme indiqué précédemment None est un objet particulier. Il n’est pas l’équivalent de null dans d’autres langages. Ainsi l’application des opérateurs avec None permet d’évaluer si une variable contient None ou si un objet est égal à None:

Si val = None:

>>> val == None
True

>>> val != None
False

>>> None == 0
False      # car on ne peut pas utiliser cette opération pour comparer à 0

>>> None == []
False      # car une liste vide ne correspond pas à None

>>> None == False
False

>>> None > 0
TypeError: '>' not supported between instances of 'NoneType' and 'int'

if…then…else

Le bloc conditionnel if...then...else s’utilise de cette façon:

if <expression à évaluer>:
    <code exécuté si vrai>

Avec else:

if <expression à évaluer>:
    <code exécuté si vrai>
else:
    <code exécuté si faux>

Par exemple:

if number == 5:
    print("number is 5")
else:
    print("number is not 5")

elif

elif permet d’imbriquer plusieurs conditions:

if <expr 1 à évaluer>:
    <code exécuté si expr 1 vrai>
elif <expr 2 à évaluer>:
    <code exécuté si expr 2 vrai>
elif <expr 3 à évaluer>:
    <code exécuté si expr 2 vrai>
...
else:
    <code exécuté si toutes les conditions sont fausses>

Par exemple:

if number > 5:
    print("number is more than 5")
elif number < 5:
    print("number is less than 5")
elif number == 5:
    print("number is 5")
else:
    print("cannot compare to 5")

Version condensée

La version condensée de if...then...else est:

<si vrai> if <condition> else <si faux>

Par exemple:

a = 2
result = ''

if a == 3:
    result = 'OK'
else:
    result = 'KO'

Cette version est équivalente à:

result = 'OK' if a == 3 else 'KO'

bool()

bool() permet de convertir la valeur d’un objet en booléen:

bool(<objet à convertir>).

Suivant la valeur que les objets peuvent prendre, ils peuvent être Falsy ou Truthy.

Opérateurs booléens

Les opérateurs booléens sont: and, or et not pour respectivement le ET logique; OU logique et pour la négation logique.

  • Si on combine plusieurs opérateurs dans une expression à évaluer, on peut utiliser des parenthèses pour se prémunir de la distributivité:
    (<expression 1>) or ((<expressions 2>) and (<expression 3>))
    
  • Lors de l’évaluation d’une expression comme celle-ci:
    <expression 1> and <expressions 2> and <expression 3>
    

    Les expressions sont évaluées successivement dans l’ordre d’apparition. Si une expression est fausse, les évaluations s’arrêtent et les expressions suivantes ne sont pas évaluées. Ainsi, si expression 1 est fausse, alors il n’y aura d’évaluation de l’expression 2 et 3.

  • De même avec une expression du type:
    <expression 1> or <expressions 2> or <expression 3>
    

    Si expression 1 est vraie, les autres expressions ne sont pas évaluées.

    Par exemple:

    >>> 5 > 2 or unknown == 9
    True
    
    >>> (5 < 2) or ((1 < 9) and (9 > 2))
    True
    
    >>> 5 > 2 and 1 < 9 and 9 > 2
    True
    

Ces opérateurs peuvent être utilisés directement avec if...then...else:

number = 3
bool_value = True
if number == 3 and bool_value:
    print("OK")
if number == 17 or not bool_value:
    print("OK")

Les collections

Parmi les collections en Python, on distingue:

  • Les listes: structure ordonnée mutable dont les éléments sont atteignables avec un index. Une liste peut être définie avec [].
  • Les tuples: structure non ordonnée immutable dont les éléments sont atteignables avec leur nom. Un tuple peut être défini avec ().
  • Les dictionnaires: structure non ordonnée mutable dont les éléments sont atteignables avec une clé. Un dictionnaire peut être défini avec {}.
  • Les ensembles (i.e. set): structure non ordonnée mutable dont les éléments ne sont pas directement atteignables. La structure peut être parcourue. Un ensemble peut être défini avec set().

Liste

Une liste peut être initialisée de ces façons:

names = []   # liste vide
names = ['a', 'b', 'c']

On peut atteindre un élément dans la liste en utilisant son index (l’index commence à 0):

>>> print(names[1])
'b'

Une liste est mutable, on peut modifier un élément:

names[0] = 'd'
Les listes ne sont pas typées

On peut ajouter des types différents dans une liste. Les types des éléments ne sont pas obligatoirement les mêmes. Il faut être vigilant sur le type des objets ajoutés à la liste.

>>> elements = [1, '3', 1.5]
>>> type(elements[0])
int

>>> type(elements[1])
str

list()

list() est le constructeur pour créer une nouvelle liste. Pour créer une liste vide, on peut exécuter:

empty_list = list()

Ou plus simplement:

empty_list = []

Si on utilise list() avec une chaîne de caractères, on obtient une liste avec tous les caractères de la chaîne:

>>> caracter_list = list('ABCDEF')
>>> print(caracter_list)
['A', 'B', 'C', 'D', 'E', 'F']

Index

En plus des index normaux, on peut utiliser des index négatifs:
-1 signifie le 1er élément en partant de la fin de la liste:

>>> names = ['a', 'b', 'c', 'd', 'e', 'f']
>>> print(names[-1])
'f'

>>> print(names[-2])
'e'

La syntaxe générale des index est:

[<index de début>:<index de fin exclu>:<pas utilisé>]

Par exemple:

>>> names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
>>> names[1:8:2]   # Commence à l'index 1 et s'arrête à l'index 7 (l'index 8 est exclu)
# L'incrément se fait en ajoutant 2 à l'index courant
['b', 'd', 'f', 'h']

Les différents arguments de l’index sont facultatifs:

  • [2:] permet de commencer à l’index 2 (3e élément) jusqu’au dernier.
  • [:3] permet de commencer du début jusqu’à l’index 2 (3e élément). L’index est exclu.
  • [:] désigne tous les éléments de la liste. Cette syntaxe permet d’effectuer une copie de la liste.

On peut utiliser des index négatifs:

>>> names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
>>> names[1:-1]  # On commence à l'index 1 et on s'arrête à l'avant dernier élément (le dernier est exclu)
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

Affectation de plusieurs éléments (list slicing)

On peut affecter plusieurs éléments en une seule ligne en utilisant les index.

Par exemple:

>>> names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
>>> names[1:3] = ['B', 'C']     #  on affecte directement des éléments aux index 1 et 2 (3 est exclu)
>>> print(names)
 ['a', 'B', 'C', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

Passage par référence

Les listes sont manipulées par référence, par exemple:

>>> names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
>>> other_names = names  # passage par références
>>> names[3] = 'NEW'
>>> print(other_names)
['a', 'b', 'c', 'NEW', 'e', 'f', 'g', 'h', 'i', 'j']

Ajouter des éléments

Pour ajouter des éléments à une liste, plusieurs syntaxes sont possibles:

  • Avec append():
    >>> names = ['a', 'b', 'c']
    >>> names.append('d')
    >>> print(names )
    ['a', 'b', 'c', 'd']
    
  • Avec +: ATTENTION cette syntaxe ne modifie pas la liste mais en crée une nouvelle
    >>> names = ['a', 'b', 'c']
    >>> other_names = names + ['d'] # names n'est pas modifiée
    >>> print(other_names)
    ['a', 'b', 'c', 'd']
    
  • Avec insert():
    La syntaxe de insert() est: insert(<index de l'élément à ajouter>, <élément à insérer>), par exemple:

    >>> names = ['a', 'b', 'c']
    >>> names.insert(1, 'NEW')
    >>> print(names)
    ['a', 'NEW', 'b', 'c']
    

Supprimer un élément

Plusieurs possibilités pour supprimer un élément d’une liste:

  • Avec del: il faut disposer de l’index de élément à supprimer
    >>> names = ['a', 'b', 'c']
    >>> del names[1]
    >>> print(names)
    ['a', 'c']
    
  • On peut trouver l’index en utilisant la fonction index():
    >>> names = ['a', 'b', 'c']
    >>> b_index = names.index('b')
    >>> del names[b_index]
    

    Plus directement:

    >>> del names[names.index('b')]
    >>> print(names)
    ['a', 'c']
    
  • Avec remove():
    >>> names = ['a', 'b', 'c']
    >>> names.remove('b')
    >>> print(names)
    ['a', 'c']
    

Si on essaie de supprimer un élément qui n’existe pas, une erreur ValueError est générée.

Effectuer une copie d’une liste

Plusieurs syntaxes sont possibles pour effectuer une copie:

  • Avec copy():
    >>> names = ['a', 'b', 'c']
    >>> names_copy = names.copy()
    >>> print(names_copy)
    ['a', 'b', 'c']
    
  • En construisant une nouvelle liste avec list():
    >>> names = ['a', 'b', 'c']
    >>> names_copy = list(names)
    >>> print(names_copy)
    ['a', 'b', 'c']
    
  • Avec l’index [:]:
    >>> names = ['a', 'b', 'c']
    >>> names_copy = names[:]
    ['a', 'b', 'c']
    

Des copies des listes sont effectuées toutefois les éléments de la liste ne sont pas dupliqués. Les éléments de la liste étant stockés par référence, la copie de la liste duplique les références mais pas les éléments vers lesquels pointent les références.

len()

La fonction len() permet de renvoyer la taille de la liste:

>>> names = ['a', 'b', 'c']
>>> print(len(names))
3

count()

count() permet de compter le nombre d’occurrences d’un élément dans la liste. Il ne faut pas confondre count() et len(), count() ne permet pas de retourner le nombre d’éléments de la liste:

>>> names = ['a', 'b', 'c', 'b', 'd', 'b']
>>> print(names.count('b'))
3

>>> print(names.count())
ERREUR

Concaténer des listes

Plusieurs syntaxes sont possibles pour effectuer une concaténation de listes:

  • Avec l’opérateur +: cet opérateur crée une nouvelle liste et ne modifie pas une liste existante:
    >>> first = [1, 2, 3, 4]
    >>> second = [5, 6, 7, 8]
    >>> print(first + second)
    [1, 2, 3, 4, 5, 6, 7, 8]
    
  • Avec extend():

    extend() modifie la liste dans laquelle elle est exécutée:

    >>> names = ['a', 'b', 'c']
    >>> names.extend(['d', 'e', 'f'])
    >>> print(names)
    ['a', 'b', 'c', 'd', 'e', 'f']
    

Répéter le contenu d’une liste (avec *)

L’opérateur * permet de répéter le contenu d’une liste en générant une nouvelle liste:

>>> names = ['a', 'b', 'c']
>>> print(names * 3)
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']

in

L’opérateur in permet de vérifier si un élément est dans une liste et renvoie True si c’est le cas:

>>> names = ['a', 'b', 'c']
>>> print('c' in names)
True

Liste de listes

On peut imbriquer des listes les unes dans les autres, par exemple:

>>> nested_list = [['1', '2', '3', '4'], ['a', 'b', 'c', 'd'], ['α', 'β', 'γ', 'δ']]
>>> print(nested_list[1])
>>> print(nested_list)
['a', 'b', 'c', 'd']

On peut accéder aux éléments en utilisant 2 index:

>>> print(nested_list[1][0])
'a'

Inverser l’ordre des éléments

2 syntaxes permettent d’inverser l’ordre des éléments directement:

  • Avec reverse(): cette fonction modifie la liste dans laquelle elle est exécutée:
    >>> names = ['a', 'b', 'c']
    >>> names.reverse()
    >>> print(names)
    ['c', 'b', 'a']
    
  • Avec reversed(): cette fonction permet de créer un itérateur permettant de parcourir la liste dans un ordre inversé:
    >>> names = ['a', 'b', 'c']
    >>> names_reversed_it = reversed(names)
    # names_reversed_it est un itérateur
    
    >>> print(type(names_reversed_it))
    <class 'list_reverseiterator'>
    

    On peut parcourir avec l’itérateur:

    for name in names_reversed_it:
        print(name)
    
    'c'
    'd'
    'e'
    

    On peut créer une nouvelle liste avec list():

    >>> names = ['a', 'b', 'c']
    >>> names_reversed_it = reversed(names)
    >>> names_reversed = list(names_reversed_it)
    >>> print(names_reversed)
    ['c', 'b', 'a']
    

Ordonner les éléments de la liste

Plusieurs syntaxes sont possibles pour ordonner les éléments de la liste:

  • Avec sort(): cette fonction modifie la liste dans laquelle elle est exécutée, par défaut sort() ordonne par ordre alphabétique croissant:
    >>> names = ['d', 'j', 'h', 'c', 'g', 'b', 'a', 'f', 'i', 'e']
    >>> names.sort()
    >>> print(names)
    ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
    
  • Avec sorted(): cette fonction permet de créer une autre liste qui sera ordonnée, par défaut sorted() ordonne par ordre alphabétique croissant:
    >>> names = ['d', 'j', 'h', 'c', 'g', 'b', 'a', 'f', 'i', 'e']
    >>> names_sorted = names.sorted()
    >>> print(names)
    >>> print(names_sorted)
    ['d', 'j', 'h', 'c', 'g', 'b', 'a', 'f', 'i', 'e']
    ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
    

    Il est possible d’utiliser cette syntaxe:

    >>> names_sorted = sorted(names)
    

sort() et sorted() autorisent des options:

  • reverse=True pour ordonner par ordre alphétique décroissant:
    >>> names.sort(reverse=True)
    >>> print(names)
    ['j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
    
  • key: permet d’effectuer l’ordonnancement suivant l’exécution d’une fonction particulière sur chaque élément de la liste.
    • Par exemple si on considère une liste de chaînes de caractères et si on applique la fonction len() sur les éléments de la liste:
      >>> words = [ 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta']
      >>> words.sort(key=len)
      >>> print(words)
      ['Beta', 'Zeta', 'Alpha', 'Gamma', 'Delta', 'Epsilon']
      
    • Avec une lambda: dans cet exemple, on ordonne suivant la 2e lettre de chaque chaîne:
      >>> words = [ 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta']
      >>> words.sort(key=lambda str: str[1])
      >>> print(words)
      ['Gamma', 'Beta', 'Delta', 'Zeta', 'Alpha', 'Epsilon']
      
    • Avec itemgetter(): cette fonction prend en paramètre un index et renvoie l’élément correspondant à l’index:
      >>> from operator import itemgetter
      >>> f = itemgetter(2)  # Renvoie la 3e lettre
      >>> str = 'example'
      >>> f(str)
      'a'
      

      Si on applique avec key: dans cet exemple le tri se fait suivant la 3e lettre de chaque chaîne:

      >>> words = [ 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta']
      >>> words.sort(key=itemgetter(2))
      >>> print(words)
      ['Delta', 'Gamma', 'Alpha', 'Epsilon', 'Beta', 'Zeta']
      
    • Avec attrgetter(): cette fonction prend en paramètre une liste de noms d’attributs et renvoie un tuple avec les valeurs correspondantes.
    • Avec methodcaller(): cette fonction prend en paramètre le nom d’une fonction et renvoie l’exécution de cette fonction sur un objet particulier:
      >>> from operator import methodcaller
      >>> f = methodcaller('index', 'a')  # Renvoie index('a')
      >>> str = 'example'
      >>> f(str)
      2   # Index 2 car 'a' est la 3e lettre de 'example'
      

      Si on applique avec key: dans cet exemple le tri se fait suivant la position de la lettre 'a' dans les chaînes:

      >>> words = [ 'Alpha', 'Beta', 'Gamma', 'Delta', 'Zeta']
      >>> words.sort(key=methodcaller('index', 'a'))
      >>> print(words)
      ['Gamma', 'Beta', 'Zeta', 'Alpha', 'Delta']
      

Déconstruction

L’opération de déconstruction est possible avec une liste.

Par exemple si on considère la liste suivante:

words = [ 'Alpha', 'Beta', 'Gamma']

On peut effectuer une déconstruction dans les objets en exécutant:

>>> word1, word2, word3 = words
>>> print(word1)
'Alpha'

>>> print(word2)
'Beta'

>>> print(word3)
'Gamma'

Tuples

Un tuple est une collection immutable. Comme pour les listes, les éléments sont accessibles en utilisant un index et un tuple peut contenir des objets de type différent.

Par exemple:

>>> tuple_example = ('A', 1, 1.0)   # Ce tuple contient 3 éléments
>>> print(tuple_example[1])
'A'

>>> print(tuple_example[2])
1

>>> print(tuple_example[3])
1.0

Les parenthèses sont facultatives:

>>> tuple_example = 'A', 1, 1.0
>>> type(tuple_example)
tuple

Un tuple vide se définit de cette façon:

empty_tuple = ()

Le tuple étant immutable, il n’est pas possible d’y ajouter ou de supprimer des éléments.

tuple()

Ce constructeur permet de créer des tuples:

  • Un tuple vide:
    empty_tuple = tuple()
    
  • Un tuple à partir d’une liste:
    >>> words = [ 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta']
    >>> word_tuple = tuple(words)
    >>> print(word_tuple)
    ('Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta')
    
  • Un tuple à partir d’une chaîne de caractères:
    >>> word = 'example'
    >>> word_tuple = tuple(word)
    >>> print(word_tuple)
    ('e', 'x', 'a', 'm', 'p', 'l', 'e')
    

len()

Cette fonction renvoie la taille d’un tuple:

>>> tuple_example = ('A', 1, 1.0)
>>> print(len(tuple_example))
3

count()

Permet de compter le nombre d’occurences d’un élément dans un tuple:

>>> letters = ('a', 'b', 'c', 'b', 'd', 'b')
>>> print(letters.count('b'))
3

Concaténation d’un tuple (avec +)

On peut utiliser + pour concatener un tuple avec un tuple. Le résultat fournit un 3e tuple:

>>> tuple1 = ('Alpha', 'Beta', 'Gamma')
>>> tuple2 = ('a', 'b', 'c')
>>> result_tuple = tuple1 + tuple2
>>> print(result_tuple)
('Alpha', 'Beta', 'Gamma', 'a', 'b', 'c')

Répéter le contenu d’un tuple (avec *)

L’opérateur * permet de répéter le contenu d’un tuple. Un nouveau tuple est généré:

>>> letters = ('a', 'b', 'c')
>>> print(letters * 3)
('a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c')

in/not in

L’opérateur in permet de vérifier si un élément est dans un tuple. Il renvoie True si c’est le cas:

>>> letters = ('a', 'b', 'c')
>>> print('c' in letters)
True

A l’opposé, not in renvoie True si un élément n’est pas dans un tuple:

>>> letters = ('a', 'b', 'c')
>>> print('d' not in letters)
True

Tuple de tuples

On peut imbriquer des tuples les uns dans les autres, par exemple:

>>> nested_tuple = (('1', '2', '3', '4'), ('a', 'b', 'c', 'd'), ('α', 'β', 'γ', 'δ'))
>>> print(nested_tuple[1])
('a', 'b', 'c', 'd')

Pour accéder aux éléments, il faut utiliser 2 index:

>>> print(nested_tuple[1][2])
c

zip()

Cette fonction permet de créer des tuples à partir des éléments de listes.

Par exemple:

index_list = [1,2,3,4]
element_list = ['a','b','c','d']
zip_object = zip(index_list, element_list)

L’objet zip_object est de type zip. On peut créer une liste à partir de cet objet pour obtenir une liste de tuples:

>>> items = list(zip_object)
>>> print(items)
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

Déconstruction

La déconstruction d’un tuple permet d’effectuer en une ligne des affectations des éléments d’un tuple dans des objets séparés.

Par exemple:

words = ( 'Alpha', 'Beta', 'Gamma')

On peut effectuer une déconstruction dans les objets en exécutant:

>>> word1, word2, word3 = words
>>> print(word1)
'Alpha'

>>> print(word2)
'Beta'

>>> print(word3)
'Gamma'

Dictionnaire

Un dictionnaire est une structure dont les éléments sont sockés sous forme de clé/valeur. Les valeurs d’un dictionnaire peuvent être atteintes en utilisant les clés correspondantes. La clé doit être unique pour chaque éléments et doit être immutable.

Un dictionnaire est un objet mutable.

A partir de Python 3.7, l’ordre de parcours des éléments d’un dictionnaire est garanti.

Pour initialiser un dictionnaire, il faut utiliser les caractères {}:

persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }

Les clés sont '1', '2', '3' et '4', les valeurs sont 'Mark', 'Elon', 'Jeff' et 'Bill'.

Pour obtenir une valeur à partir de sa clé:

>>> print(persons['2'])
'Elon'

Un dictionnaire peut être initialisé de cette façon:

empty_dictionary = {}

Comme pour les listes, on peut stocker des objets de type différent dans un dictionnaire aussi bien pour les clés que pour les valeurs:

persons = { '1': 'Mark', 2: 'Elon', 3.0: 'Jeff', '4': 4  }

Si on utilise un clé qui n’existe pas, une erreur KeyError est levée:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> persons['5']
KeyError: '5'

Modifier une valeur

Comme le dictionnaire est mutable, on peut en modifier une valeur en utilisant l’index avec une clé.

Par exemple:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> persons['1'] = 'Guido'
>>> print(persons)
{'1': 'Guido', '2': 'Elon', '3': 'Jeff', '4': 'Bill'}

Si la clé n’existe pas dans le dictionnaire, une nouvelle valeur sera rajoutée:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> persons['5'] = 'Guido'
>>> print(persons)
{'1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill', '5': 'Guido'}

update()

La fonction update() peut être utilisée pour rajouter ou modifier les valeurs dans un dictionnaire:

  • Si la clé existe alors la valeur est remplacée
  • Si la clé n’existe pas, le couple clé/valeur est rajouté.

Par exemple:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> new_persons = [('1', 'Guido'), ('5', 'Grace')]
>>> persons.update(new_persons)
>>> print(persons)
{'1': 'Guido', '2': 'Elon', '3': 'Jeff', '4': 'Bill', '5': 'Grace'}
# La clé '1' existait déjà et a été modifié. La clé '5' n'existait pas.

Supprimer une clé/valeur

Pour supprimer une clé et la valeur correspondante dans un dictionnaire, il faut utiliser l’opérateur del. Le couple clé/valeur est directement supprimé dans le dictionnaire, par exemple:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> del persons['1']
>>> print(persons)
{'2': 'Elon', '3': 'Jeff', '4': 'Bill'}

get()

Cette fonction permet de récupérer une valeur dans un dictionnaire sans qu’une erreur ne soit levée si la clé n’existe pas. La syntaxe de get() est:

<valeur ou valeur de retour> = <dictionnaire>.get(<clé>, <valeur de retour si la clé n'existe pas>)

Le paramètre <valeur de retour si la clé n'existe pas> est facultatif. S’il n’est pas présent, la valeur retournée est None si la clé n’existe pas dans le dictionnaire.

Par exemple:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> print(persons.get('3'))
'Jeff'   # La clé existe alors la valeur correspondante est retournée

>>> print(persons.get('5'))
None      # La clé '5' n'existe pas donc None est retournée

>>> print(persons.get('5', 'Unknown'))
'Unknown'    #  La clé '5' n'existe pas donc la valeur par défaut est retournée

dict()

dict() est un constructeur permettant de créer un dictionnaire:

  • Un dictionnaire vide:
    empty_dict = dict()
    
  • Créer un nouveau dictionnaire à partir d’un dictionnaire existant:
    >>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
    >>> persons_copy = dict(persons)
    >>> print(id(persons))
    281472705125760
    
    >>> print(id(persons_copy))
    281472704300608
    
  • A partir d’une liste de tuple:
    >>> tuple_list = [( '1', 'Mark'), ('2', 'Elon'), ('3', 'Jeff'), ('4', 'Bill')]
    >>> persons = dict(tuple_list)
    >>> print(persons)
    {'1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'}
    
  • Le constructeur peut être utilisé en indiquant les clés/valeurs avec la syntaxe <clé> = <valeur>:
    >>> persons = dict(key1 = 'Mark', key2= 'Elon', key3= 'Jeff', key4= 'Bill')
    >>> print(persons)
    {'key1': 'Mark', 'key2': 'Elon', 'key3': 'Jeff', 'key4': 'Bill'}
    

keys()

keys() est une fonction du dictionaire permettant de retourner un objet itérable contenant toutes les clés.

Par exemple:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> persons_keys = persons.keys()
>>> type(persons_keys)
dict_keys   # Le type retourné est dict_keys.

Pour obtenir une liste à partir de cet objet, on peut utiliser le constructeur list():

>>> key_list = list(persons_keys)
>>> print(key_list)
['1', '2', '3', '4']

values()

values() est une fonction du dictionnaire renvoyant un objet itérable contenant toutes les valeurs du dictionnaire.

Par exemple:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> persons_values = persons.values()
>>> type(persons_values)
dict_values

Pour obtenir une liste à partir de cet objet, on peut utiliser le constructeur list():

>>> value_list = list(persons_values)
>>> print(value_list)
['Mark', 'Elon', 'Jeff', 'Bill']

Parcourir les valeurs d’un dictionnaire

L’objet dictionnaire est itérable. A partir de Python 3.7, l’ordre de parcours d’un dictionnaire est garanti. Si on itère directement sur un dictionnaire, on itére sur les clés:

persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
for person_key in persons:
    print(person_key)
1
2
3
4

On peut itérer directement sur les clés et les valeurs en utilisant items(). items() permet de créer un itérable de tuples sur les objets du dictionnaire:

for person_key, person_value in persons.items():
    print('Key: %s/Value: %s' % (person_key, person_value))
Key: 1/Value: Mark
Key: 2/Value: Elon
Key: 3/Value: Jeff
Key: 4/Value: Bill

Pour avoir la liste de tuples, on peut exécuter:

>>> values_list = list(persons.items())
>>> print(values_list)
[('1', 'Mark'), ('2', 'Elon'), ('3', 'Jeff'), ('4', 'Bill')]

copy()

copy() permet d’effectuer une copie d’un dictionnaire. Les références sont copiées mais les éléments ne sont pas dupliqués.

Par exemple:

>>> persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }
>>> persons_copy = persons.copy()
>>> print(persons_copy)
{'1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'}

Dictionnaires imbriqués

Il est possible de construire des dictionnaires imbriqués. L’accès aux valeurs se fait en utilisant plusieurs index, par exemple:

pilots = { 'first': {'name': 'Armstrong', 'firstname': 'Neil'},
'second': {'name': 'Aldrin', 'firstname': 'Buzz'},
'third': {'name': 'Collins', 'firstname': 'Michael'}}
print(pilots)
{'first': {'name': 'Armstrong', 'firstname': 'Neil'}, 'second': {'name': 'Aldrin', 'firstname': 'Buzz'}, 'third': {'name': 'Collins', 'firstname': 'Michael'}}

Pour accéder à un élément:

>>> print(pilots['second']['firstname'])
Buzz

in/not in

L’opérateur in permet de vérifier si un élément est parmi les clés d’un dictionnaire. Il renvoie True si c’est le cas:

>>> persons = {'1': 'Guido', '2': 'Elon', '3': 'Jeff', '4': 'Bill', '5': 'Grace'}
>>> print('2' in persons)
True

>>> print('2' not in persons)
False

set

Un set (i.e. ensemble) est une structure non ordonnée d’objets uniques. Un set est un objet mutable. Les objets ajoutés dans un set doivent être uniques et immutables.

Pour initialiser un set, il faut utiliser les caractères {}:

persons = { 'Guido', 'Ada', 'Alan', 'Bjarne', 'Grace' }

on ne peut pas instancier un set vide de cette façon:

empty_set = {}     # dictionnaire

empty_set est, dans ce cas, un dictionnaire vide. Pour instancier un set vide, il faut utiliser le constructeur:

empty_set = set()

Un dictionnaire peut être initialisé de cette façon:

empty_dictionary = {}

Les objets dans un set peuvent être de type différent toutefois ils doivent être uniques. Ainsi:

>>> object_set = { '1', '2', 1, False, 2.0 }
>>> print(object_set)
{False, 1, 2.0, '2', '1'}

L’ordre des objets n’est pas le même.

Si on tente d’ajouter True, l’objet ne sera pas ajouté:

>>> object_set.add(True)
>>> object_set.add(1.0)
>>> print(object_set)
{False, 1, 2.0, '2', '1'}

Car 1 == True et 1 == 1.0

Les objets ajoutés doivent être immutables. Par exemple, si on tente d’ajouter une liste (qui est un objet mutable):

>>> object_set.add([0, 1])
TypeError: unhashable type: 'list'

set()

Le constructeur permet de créer un nouveau set:

empty_set = set()

On peut créer un set à partir d’un autre set, d’une liste, d’un tuple, d’un dictionnaire ou d’une chaîne de caractères:

>>> list_with_duplicates = [1, 2, 3, 2, 1, 4, 1, 3, 2]    # Liste
>>> set_without_duplicates = set(list_with_duplicates)
>>> print(set_without_duplicates)
{1, 2, 3, 4}

Les duplicats n’ont pas été ajoutés.

>>> tuple_example = (1, 2, 3, 2, 1, 4, 1, 3, 2)     # Tuple
>>> set_without_duplicates = set(tuple_example)
>>> print(set_without_duplicates)
{1, 2, 3, 4}

>>> dictionary_example = {1: 'One', 2: 'Two', 3: 'Three'}
>>> set_from_dictionary = set(dictionary_example)
>>> print(set_from_dictionary)
{1, 2, 3}

Seulement les clés sont rajoutées au set:

>>> string_example = 'Example of string'
>>> set_from_string = set(string_example)
>>> print(set_from_string)
{'a', 'e', 'n', 'i', 'f', 's', 'm', 'x', 'p', 'E', 'l', ' ', 'r', 'g', 'o', 't'}

>>> set1 = {1, 2, 3, 4}
>>> set2 = set(set1)
>>> print(set2)
{1, 2, 3, 4}

add()

Permet de rajouter un élément à un set. Si l’élément est déjà présent, il ne sera pas rajouté:

>>> set_example = { 1, 2, 3}
>>> set_example.add(1)
>>> print(set_example)
{1, 2, 3}

>>> set_example.add(True)
>>> print(set_example)
{1, 2, 3}

True n’est pas rajouté car 1 == True.

update()

update() permet de rajouter plusieurs éléments dans un set à partir d’un itérable (liste, tuple, dictionnaire etc…):

>>>set_example = { 1, 2, 3}
>>> set_example.update([ 2, 3, 4, 5])
>>> print(set_example)
{1, 2, 3, 4, 5}

Les duplicats ne sont pas rajoutés.

A partir d’un dictionnaire, seulement les clés sont itérées:

>>> set_example = { 1, 2, 3}
>>> set_example.update({2: 'Two', 3: 'Three', 4: 'Four'})
>>> print(set_example)
{1, 2, 3, 4}

Supprimer un élément d’un set

On peut utiliser plusieurs méthodes pour supprimer un élément d’un set:

  • remove(<élément à supprimer>):

    Par exemple:

    >>> set_example = { 1, 2, 3}
    >>> set_example.remove(2)
    >>> print(set_example)
    {1, 3}
    

    Si l’élément n’existe pas, une erreur est générée:

    >>> set_example.remove(4)
    ERREUR: KeyError: 4
    
  • discard(<élément à supprimer>):
    discard() permet de supprimer un élément sans générer d’erreur si l’élément n’existe pas dans le set.

    Par exemple:

    >>> set_example = { 1, 2, 3}
    >>> set_example.discard(4)
    >>> print(set_example)
    {1, 2, 3}
    

Effectuer une copie d’un set

On peut utiliser 2 syntaxes:

  • copy(): par exemple:
    >>> set1 = { 1, 2, 3, 4}
    >>> set2 = set1.copy()
    >>> print(set2)
    { 1, 2, 3, 4}
    
    >>> set1.remove(2)
    >>> print(set1)
    >>> print(set2)
    { 1, 3, 4}
    { 1, 2, 3, 4}   # set2 n'est pas modifié
    
  • set(): utiliser le constructeur permet de copier un set:
    >>> set1 = { 1, 2, 3, 4}
    >>> set2 = set(set1)
    >>> print(set2)
    { 1, 2, 3, 4}
    

in/not in

L’opérateur in permet de vérifier si un élément se trouve dans un set. Il renvoie True si c’est le cas:

>>> set_example = { 1, 2, 3, 4, 5 }
>>> print(2 in set_example)
True

>>> print(2 not in set_example)
False

Opérations applicables sur les sets

On peut appliquer des opérations ensemblistes sur les sets, par exemple si on considère les sets:

set1 = {1, 2, 3}
set2 = {2, 3, 4}

Chaque opération génère un set et ne modifie pas le set à partir duquel la fonction est exécutée:

  • union(): retourne un set comportant les éléments uniques de set1 et set2:
    >>> print(set1.union(set2))
    {1, 2, 3, 4}
    
  • intersection(): retourne un set comportent les éléments communs entre set1 et set2:
    >>> print(set1.intersection(set2))
    {2, 3}
    
  • intersection_update(): même fonction que intersection() mais set1 est modifié:
    >>> set1.intersection_update(set2)
    >>> print(set1)
    {2, 3}
    
  • difference(): retourne un set avec les éléments de set1 après avoir supprimé les éléments se trouvant dans set2:
    >>> print(set1.difference(set2))
    {1}
    
  • difference_update(): même fonction que difference() mais set1 est modifié:
    >>> set1.difference_update(set2)
    >>> print(set1)
    {1}
    
  • symmetric_difference(): retourne un set avec les éléments de set1 après avoir supprimé les éléments se trouvant dans set2. Les éléments de set2 ne se trouvant pas dans le set1 sont ajoutés.
    >>> print(set1.symmetric_difference(set2))
    {1, 4}
    
  • symmetric_difference_update(): même fonction symmetric_difference() mais set1 est modifié:
    >>> set1.symmetric_difference_update(set2)
    >>> print(set1)
    {1, 4}
    
  • issubset(): retourne True si set1 est un sous-ensemble de set2:
    >>> print(set1.issubset(set2))
    False
    
    >>> set2 = {2, 3, 4}
    >>> set3 = {2, 3}
    >>> print(set3.issubset(set2))
    True
    
  • isdisjoint(): retourne True si aucun élément n’est commun entre set1 et set2:
    >>> print(set1.isdisjoint(set2))
    False
    
    >>> set2 = {2, 3, 4}
    >>> set4 = {1, 5}
    >>> set2.isdisjoint(set4)
    True
    

Itérable

Un itérable est un objet dont on peut parcourir les objets avec une boucle for. Les structures comme les listes, les sets, les dictionnaires ou les tuples sont des itérables. Ainsi:

  • Un itérable: on peut obtenir un iterator à partir d’un itérable en utilisant la fonction iter():
    iterator = iter(iterable)
    
  • Un iterator: objet implémentant le design pattern iterator. Appliqué sur un itérable, un iterator permet d’obtenir l’élément suivant en utilisant la fonction next():
    item = next(iterator)
    

Par exemple, si on considère la liste suivante:

>>> iterable = ['Spring', 'Summer', 'Autumn', 'Winter']
>>> iterator = iter(iterable)
>>> next(iterator)

On obtient:

  • 1ère exécution: 'Spring',
  • 2e exécution: 'Summer',
  • 3e exécution: 'Autumn',
  • 4e exécution: 'Winter'
  • 5e exécution: à la fin, si on exécute l’itérateur pour avoir l’élément suivant, on obtient une exception StopIteration. Cette erreur survient si l’iterable est vide.

Les fonctions suivantes s’appliquent sur des itérables:

  • all(): renvoie True si tous les éléments de l’itérable sont considérés comme vrai (au sens Truthy/Falsy).
  • any(): renvoie True si au moins un élément de l’iterable est considéré comme vrai (au sens Truthy/Falsy).

Fonctions

Pour définir une fonction, on utilise le mot-clé def:

def <nom fonction>(<arguments>):
    <corps de la fonction>

Par exemple:

def get_title_case(input):
    return input.title()
>>> print(get_title_case('example'))
Example

Dans le cas d’une méthode (qui ne renvoie rien), le retour est None:

def print_with_title_case(input):
    print(input.title())
>>> result = print_with_title_case('example')
>>> print(result)
None

Arguments

Les arguments sont passés par référence.

Par exemple si on considère la fonction suivante:

def remove_first_item(items):
    del items[0]

Si on effectue l’exécution suivante:

items = [1, 2, 3, 4]
print(items)
remove_first_item(items)
print(items)

Comme l’objet items est passé en paramètre de la fonction par référence, la modification à l’intérieur de méthode modifie directement la liste.

Paramètre par défaut

On peut indiquer la valeur par défaut de paramètres. Si l’argument n’est pas précisé lors de l’appel de la fonction c’est la valeur par défaut qui sera utilisée.

Par exemple, si on considère la méthode:

def remove_item(items, index = 0):
    del items[index]

On peut préciser une valeur pour l’argument index ou l’omettre:

>>> items = [1, 2, 3, 4]
>>> remove_item(items)
>>> print(items)
[2, 3, 4]

L’élément à l’index 0 a été supprimé

>>> items = [1, 2, 3, 4]
>>> remove_item(items, 2)
>>> print(items)
[1, 2, 4]

L’élément à l’index 2 a été supprimé

L’argument par défaut est évalué quand la fonction est lue à l’exécution par le runtime

Par exemple si on considère cette méthode:

import time
def print_current_time(arg=time.ctime()):
    print(arg)

Si on exécute cette méthode sans préciser de paramêtres:

print_current_time()

La valeur affichée sera toujours la même car l’argument est évalué une seule fois au moment où la déclaration de la méthode est lue. Pour éviter ces problèmes, il faut privilégier des objets immutables pour les arguments par défaut.

Préciser le nom des arguments

Il est possible d’indiquer le nom des arguments lors d’un appel.

Par exemple, si on considère la méthode:

def print_strings(string1, string2, string3):
    print('string1: {0}'.format(string1))
    print('string2: {0}'.format(string2))
    print('string3: {0}'.format(string3))

On peut effectuer les appels suivants:

>>> print_strings('1', '2', '3')
>>> print_strings(string1='1', string2='2', string3='3')     # En nommant les arguments
>>> print_strings(string3='3', string2='2', string1='1')     # En changeant l'ordre des arguments
>>> print_strings('1', string2='2', string3='3')      # Il n'est pas nécessaire de nommer tous les arguments
>>> print_strings('1', '2', string3='3')
>>> print_strings('3', '2', string1='1')       # ERREUR: string1 possède plusieurs valeurs
>>> print_strings('1', string2='2', '3')       # ERREUR: si on nomme l'argument string2, il faut nommer aussi string3

Nombre variable d’arguments

On peut définir une méthode avec un nombre variable d’arguments en nommant la variable *<nom variable>, par exemple:

def var_args(name, *args):
    print(type(args))
    print(args) # args est un tuple

On peut appeler la méthode de ces façons:

  • var_args('misc', 2, 3, 4)
    <class 'tuple'>
    (2, 3, 4)
    
  • var_args('misc', *[2, 3, 4])
    <class 'tuple'>
    (2, 3, 4)
    

    Il faut faire attention à ne pas oublier * avec la liste sinon c’est comme s’il n’y avait qu’un seul argument.

    Si on omet * devant la liste: var_args('misc', [2, 3, 4])

    <class 'tuple'>
    ([2, 3, 4],)
    

    Il s’agit d’un tuple contenant un seul élément de type liste.

  • list_args = [2, 3, 4]
    var_args('misc', *list_args)

    <class 'tuple'>
    (2, 3, 4)
    

Arguments variables indiqués sous forme d’un dictionnaire

Les arguments peuvent être indiqués sous la forme d’un dictionnaire en nommant la variable **<nom variable>, par exemple:

def var_args(name, **args):
    print(type(args))
    print(args) # args est un dictionnaire

On peut appeler la méthode de ces façons:

  • var_args('misc', arg1=4, arg2=3, arg3=2)
    <class 'dict'>
    {'arg1': 4, 'arg2': 3, 'arg3': 2}
    

    Les clés sont indiquées sous forme de chaîne de caractères.

  • var_args('misc', **{'arg1': 4, 'arg2': 3, 'arg3': 2})
    <class 'dict'>
    {'arg1': 4, 'arg2': 3, 'arg3': 2}
    
  • dict_args = {'arg1': 4, 'arg2': 3, 'arg3': 2}
    var_args('john', **dict_args)

    <class 'dict'>
    {'arg1': 4, 'arg2': 3, 'arg3': 2}
    

Fonctions imbriquées

On peut définir des fonctions dans d’autres fonctions (i.e. nested function).

Par exemple:

def get_items_with_title_case():
    items = ['one', 'two', 'three']

    def get_title_case():
        items_titlecase = []

        for item in items:
            items_titlecase.append(item.title())

        return items_titlecase

    titlecases = get_title_case()
    print(titlecases)

Si on appelle la méthode:

>>> get_items_with_title_case()
['One', 'Two', 'Three']

La fonction imbriquée a accès aux variables de la fonction parente.

Fonctions de premier ordre

On peut transmettre des fonctions en paramètre d’autres fonctions, par exemple si on considère les 2 fonctions suivantes:

def print_fctn_result(n, fctn_to_execute):
    print(type(fctn_to_execute))
    for i in range(n):
        print(fctn_to_execute(i))

def power_of_2(x):
    return x ** 2

On peut effectuer l’appel en fournissant la méthode power_of_2() en tant qu’argument:

>>> print_fctn_result(10, power_of_2)
<class 'function'>
0
1
4
9
16
25
36
49
64
81

Quelques fonctions particulières

map()

map est un objet qui prend des arguments et les passent dans un autre objet, par exemple:

map_example = map(<fonction>, <arguments>)   # la liste des arguments est passée à la fonction

Par exemple si on déclare la fonction:

def addition(n):
    return n + n
>>> numbers = (1, 2, 3, 4)
>>> result = map(addition, numbers)   # result est un objet map
>>> print(list(result))               # Pour créer une liste il faut exécuter list()
[2, 4, 6, 8]

Avec une lambda:

>>> map_example = map(lambda x:x, [1, 2, 3, 4])
>>> print(map_example)
<map object at 0xffff78f2b310>

>>> print(list(map_example))
[1, 2, 3, 4]

filter()

filter() utilise une lambda renvoyant un booléen pour filtrer une liste:

Par exemple:

>>> filter_example = filter(lambda x:x<3, [1, 2, 3, 4])
>>> print(filter_example)          # filter_example est un objet de type filter
<filter object at 0xffff78f2b190>

>>> print(list(filter_example))    # il faut utiliser list() pour en créer une liste
[1, 2]

reduce()

reduce() permet d’effectuer un traitement sur tous les éléments d’un itérable et de renvoyer un seul objet à la suite de ce traitement.

En entrée, la fonction prend comme argument:

  • une fonction: cette fonction correspond au traitement qui sera appliqué à tous les éléments de l’itérable. La signature de cette fonction doit être:
    result_value = process(value, element)
    

    avec:

    • value: la valeur à retourner par la fonction reduce();
    • element: l’élément courant de l’itérable
    • result_value: le résultat du traitement de la fonction à l’élément courant de l’itérable. Pour chaque élément de l’itérable, result_value devient l’argument value de l’élément suivant.
  • un itérable: c’est la collection d’objets qui sera parcourue.

Par exemple si on considère la fonction:

def add_values(a, b):
    return a + b

Alors on peut appliquer reduce():

from functools import reduce

result = reduce(add_values, [1, 2, 3, 4])
print(result)
10

Fonction lambda

Une fonction lambda est une fonction anonyme c’est-à-dire qu’elle n’a pas de nom.

Pour définir une fonction lambda, on utilise le mot-clé lambda:

multiply_by_2 = lambda x:x*2

multiply_by_2 est le nom de la lambda; x est le seul argument de cette fonction.

Cette fonction peut être appelée comme une fonction normale:

>>> result = multiply_by_2(5)
>>> print(result)
25

Avec plusieurs arguments:

multiply_values = lambda x,y: x * y

Pour effectuer l’appel:

multiply_values(2,5)

Quelques caractéristiques des fonctions lambda en Python:

  • Elles ne peuvent contenir qu’une expression, elles ne peuvent pas contenir des déclarations.
  • Elles ne peuvent comporter qu’une seule ligne.
  • Comme pour les fonctions normales, ce sont des objets de premier ordre. Elles peuvent être transmises en argument.

Par exemple, si on considère la fonction suivante:

def handle_price_from_range(price, operation):
    if price > 1000:
        return operation(price)
    elif price > 500:
        return operation(price/2)
    elif price > 0:
        return operation(price/4)
    else:
        return price

On peut effectuer un appel:

handle_price_from_range(3000, lambda x: x / 10)

Boucles

Il existe 2 types de boucles en Python:

  • for permettant de parcourir un objet itérable (c’est-à-dire qui implémente une fonction __iter__()).
  • while qui évalue une expression avant chaque itération.

for

for permet de parcourir des objets itérables. Cette instruction n’est pas utilisée avec une variable contenant l’index de la structure à parcourir comme ça c’est le cas pour d’autres langages. for est l’équivalent de foreach dans d’autres langages.

Par exemple:

values = [1, 3, 4, 9, 2, 5]
for value in values:
    print(value)
1
3
4
9
2
5

Dans cet exemple, values est une liste qui est un objet itérable comme les dictionnaires, set, tuple, etc…

range()

En Python, il n’y a pas d’équivalent des boucles for des autres langages. L’opérateur for en Python ne permet pas d’utiliser une variable index pour parcourir une structure. for s’utilise seulement avec un objet itérable. Ainsi, pour utiliser des index avec for, on peut utiliser la fonction range() qui permet de générer facilement un objet itérable.

Par exemple:

x = 0
for index in range(10):
    x += 10
    print("The value is {0}".format(x))

Dans cet exemple, range(10) produit une liste de 10 éléments commençant par 0.

D’autres surchages existent:

  • range(5, 10) permet d’itérer la suite 5, 6, 7, 8, 9
  • range(5, 10, 2) permet d’itérer la suite 5, 7, 9

5 est le début; 10 est la fin et 2 est l’incrément.

Le résultat de range() est un objet de type range qui est itérable et donc utilisable avec for.

On peut utiliser le constructeur list() pour extraire tous les objets générés par range():

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

La construction suivante est à éviter pour itérer dans une liste:

s = [0, 1, 4, 6, 13]
for i in range(len(s)):
    print(s[i])

Il faut itérer directement sur l’objet:

for v in s:
    print(v)

while

while est le même opérateur que dans les autres langages, il permet d’évaluer une condition avant d’itérer un bloc de code:

while <expression à évaluer>:
    <bloc exécuté si expression vraie>

Par exemple:

x = 0
while x < 10:
    print("Count is {0}".format(x))
    x += 1 # Comme pour "for" il faut incrémenter soi-même.

break et continue

break et continue ont la même signification que dans les autres langages, ils permettent:

  • break: de stopper une itération
  • continue: de passer directement à l’itération suivante.

Ils peuvent être utilisé avec for et while.

Par exemple, pour stopper l’exécution d’une boucle avec break:

values = [1, 3, 4, 9, 2, 5]
for value in values:
    if (value > 5):
        break
    print(value)
1
3
4

Exemple d’utilisation de continue pour ne pas exécuter une portion de code de la boucle et passer directement à l’itération suivante:

values = [1, 3, 4, 9, 2, 5]
for value in values:
    if value > 3 and value < 6:
        continue
    print(value)
1
3
9
2

Dans cet exemple, 4 et 5 ne sont pas affichés car l’exécution de continue empêche l’exécution de la ligne print(value).

Enumérateur

Un énumérateur est une fonction native de Python permettant d’avoir un compteur automatique s’appliquant sur un itérable. Cette fonction s’utilise avec un constructeur enumerate().

Par exemple avec une liste:

>>> items = ['One', 'Two', 'Three', 'Four', 'Five']
>>> enumerate_items = enumerate(items)
>>> type(enumerate_items)
enumerate

Le type de l’objet est enumerate.

Avec une boucle for:

for item in enumerate_items:
    print(item)
(0, 'One')
(1, 'Two')
(2, 'Three')
(3, 'Four')
(4, 'Five')

On obtient des tuples contenant un compteur et l’élément correpond à l’index du compteur dans l’itérable.

On peut effectuer une décomposition:

for index, item in enumerate_items:
    print(f"{index}:{item}")
0:One
1:Two
2:Three
3:Four
4:Five
L’énumérable ne doit être exécuté qu’une fois

Si l’enumerable a été exécuté une fois dans une boucle for. L’exécution suivante ne permet pas d’obtenir une nouvelle énumération.

Par exemple, si on exécute:

items = ['One', 'Two', 'Three', 'Four', 'Five']
enumerate_items = enumerate(items)
for item in enumerate_items:
    print(type(item))

On obtient bien l’énumération.

Si on réexécute:

for item in enumerate_items:
    print(type(item))

⇒ Pas de résultat

Il faut réinstancier l’énumérable pour obtenir une nouvelle énumération.

enumerate_items = enumerate(items)

Il existe une autre surchage de enumerate() permettant de préciser l’index de départ de l’énumération:

enumerate_items = enumerate(items, 2)
for index, item in enumerate_items:
    print(f"{index}:{item}")
2:One
3:Two
4:Three
5:Four
6:Five

Comprehensions

Une comprehension est une syntaxe permettant de créer facilement une suite pouvant être:

  • une liste,
  • un dictionnaire,
  • un set ou
  • un generator.

Par exemple, pour construire une list comprehension:

[expr(item) for item in iterable]

List comprehension

Une list comprehension permet de créer une liste, la syntaxe générale est:

[<expression> for <variable> in <iterable>]

Ou avec une condition:

[<expression> for <variable> in <iterable> if <condition>]

Par exemple, si on considère la liste suivante:

elements = ['One', 'Two', 'Three', 'Four', 'Five']

On peut utiliser une list comprehension pour créer une autre liste:

new_list = [len(element)] for element in elements]
print(new_list)
[3, 3, 5, 4, 4]

On peut utiliser plusieurs boucles dans une list comprehension, par exemple:

a = ['One', 'Two', 'Three']
b = [1, 2, 3]
new_list = [(x, y) for x in a for y in b]
print(new_list)
[('One', 1), ('One', 2), ('One', 3), ('Two', 1), ('Two', 2), ('Two', 3), ('Three', 1), ('Three', 2), ('Three', 3)]

On crée des listes de tuples avec (x, y).

En rajoutant une condition:

new_list = [(x, y) for x in a for y in b if a.index(x)==b.index(y)]
print(new_list)
[('One', 1), ('Two', 2), ('Three', 3)]

D’autres exemples:

[x ** 3 for x in range(10)]
[x ** 3 for x in range(10) if x % 2]
[(a, x) for x in range(3) for a in "abc"]

Sets comprehension

Permet de créer un set avec une comprehension:

{expr(item) for item in iterable}

Par exemple:

{x ** 2 for x in range(10)}
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

Dictionary comprehension

Permet de créer un dictionnaire avec une comprehension.

La syntaxe générale est:

{key_expr: value_expr for item in iterable}

Par exemple:

items = [(1, 'One'), (2, 'Two'), (3, 'Three'), (4, 'Four'), (5, 'Five')]
{item[0]:item[1] for item in items}
{1: 'One', 2: 'Two', 3: 'Three', 4: 'Four', 5: 'Five'}
Dans le cas de clés dupliquées, les valeurs précédentes sont écrasées

Par exemple:

items = [(1, 'One'), (2, 'Two'), (2, 'Two duplicated'), (3, 'Three'), (3, 'Three duplicated')]
{item[0]:item[1] for item in items}
{1: 'One', 2: 'Two duplicated', 3: 'Three duplicated'}

On ne retrouve pas les valeurs issues des tuples (2, 'Two') et (3, 'Three').

Generators

Les generators permettent de générer des suites intégrables:

  • Ils sont évalués à la demande pour obtenir l’élément suivant (lazy evaluation)
  • Ils peuvent modéliser des suites infinies.
  • Les processus peuvent organiser dans un pipeline.
  • Un generator se définit comme une fonction traditionnelle avec le mot-clé yield.
  • Un generator est à usage unique. Si on définit un generator et qu’on l’utilise entièrement il faudra en créer un nouveau pour le réutiliser.

Par exemple:

def gen123():
    yield 1
    yield 2
    yield 3

g = gen123()

g est un generator:

  • 1er exécution next(g): 1
  • 2e exécution next(g): 2
  • 3e exécution next(g): 3
  • 4e exécution next(g): ERREUR

Fonctions generator avec état

  • Les generators permettent de reprendre l’exécution.
  • Ils maintiennent l’état des variables locales.
  • Ils sont évalués à la demande (lazy évaluation).
  • Des pipelines peuvent être implémentés en faisant des fonctions composées.

Par exemple:

def func1(arg):
    yield arg

def func2(arg):
    yield arg

En écrivant func1(func2(3)), on peut exécuter des espèces de pipeline.

On peut utiliser return pour arrêter une exécution avec yield:

Par exemple:

def take(count, iterable):
    counter = 0

    for item in iterable:
        if counter == count:
            return

        counter += 1
        yield item

Un autre avantage des generators est de permettre une exécution infinie.

Par exemple, si on fait une boucle infinie avec yield:

while True:
    yield...

On produit un objet itérable infini.

Generator comprehension (ou generator expression)

On peut définir un generator sous forme de comprehensions en utilisant la syntaxe suivante:

(expr(item) for item in iterable)

Cette syntaxe permet de fournir un generator (itérable).

Par exemple:

millions_squares = (x*x for x in range(1, 1000001))

Il suffit d’écrire avec une comprehension: sum(x*x for x in range(1, 1000001)) pour tirer partie des “generators”.

Il est aussi possible d’utiliser un prédicat:

(expr(item) for item in iterable if predicate(item))

Pour tester le generator, on peut utiliser list(<generator>), par exemple:

millions_squares = list((x*x for x in range(1, 1000001)))

Le module itertools permet de fournir des itérateurs:

  • count(start, step): commence à itérer à partir de start en ajoutant step à chaque itération. La boucle est infinie.
  • cycle(iterable): répète les valeurs de l’itérable indéfiniment.
  • repeat(val, num): répète num fois la valeur val.
  • islice(iterable, start, stop, step): renvoie les valeurs de l’itérable en commençant à l’index start, en terminant à l’index stop et en incrémentant l’index suivant la valeur step.

Pour utiliser ces fonctions il faut écrire:

from itertools import islice, count

Exceptions

Les exceptions permettent d’implémenter une gestion des erreurs en utilisant des blocs de code semblables aux try...catch.

Par exemple:

student = {
    { "name": "Mark", "student_id": 15304, "feedback": None }
}

try:
    last_name = student["last_name"]
except KeyError:
    # Cette erreur est lancée dans le cas d'une erreur KeyError.
    print("Error finding")

print("This code executes") # Ce code se trouve en dehors du try...catch et est donc toujours exécuté

persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }

try:
    unknown = persons['5']
except KeyError:
    # Cette erreur est lancée dans le cas d'une erreur KeyError.
    print("Error finding")

# Ce code se trouve en dehors du try...except et est donc toujours exécuté
print("This code executes")

La clé '5' du dictionnaire n’existe pas donc une exception KeyError est lancée et interceptée par except KeyError.

Dans cet exemple, seules les exceptions KeyError sont gérées. Les autres types d’exceptions ne sont pas gérées dans le bloc except.

Gestion de plusieurs types d’erreurs

On peut gérer plusieurs types d’exceptions en utilisant plusieurs blocs except.

Par exemple:

persons = { '1': 'Mark', '2': 'Elon', '3': 'Jeff', '4': 'Bill'  }

try:
    name = persons ['3']
    numbered_name = 3 + name
except KeyError:
    print("KeyError")
except TypeError:
    print("KO")   # Exécuté car on ne peut pas ajouté 3 à "Jeff"

On peut aussi utiliser un seul bloc except pour traiter plusieurs types d’exceptions:

try:
    name = persons ['3']
    numbered_name = 3 + name
except (KeyError, TypeError):
    print("KO")

Prendre en compte tous les types d’exceptions

Il faut utiliser un bloc:

except Exception:

Par exemple:

try:
    name = persons ['3']
    numbered_name = 3 + name
except Exception:
    print("KO")

Pour afficher l’erreur

Il faut utiliser la syntaxe:

except TypeError as error

Par exemple:

try:
    name = persons ['3']
    numbered_name = 3 + name
except TypeError as error:
    print(error) # L'erreur est affichée mais pas le numéro de ligne

Relancer une exception

Pour relancer une exception, il faut utiliser le mot-clé raise.

Par exemple:

try:
    name = persons ['3']
    numbered_name = 3 + name
except TypeError as error:
    print(error)
    raise   # Permet de relancer l'exception

Lancer une exception

Pas forcément dans un bloc try...except:

raise ValueError("<type de l'erreur>")

Quelques types d’exceptions courantes:

  • IndexError: index en dehors de l’intervalle d’une liste (out of range)
  • ValueError: objet avec le bon type mais avec une valeur qui n’est pas correcte.
  • KeyError: mauvaise clé dans un dictionnaire.
  • OSError: erreur avec l’API de l’OS (par exemple quand on lit un fichier)
  • TypeError: si on fait une opération avec des types incompatibles.

finally

Permet d’ajouter un bloc qui sera exécuté dans tous les cas c’est-à-dire dans le cas où une exception a été lancée ou non. Il suffit de prévoir un bloc finally après try...except:

Par exemple:

try:
    found_person = persons['5']
except KeyError:
    print("Error finding")
    found_person = 'unknown'
finally:
    print(found_person)

A l’exécution:

Error
finding
unknown

else

On peut utiliser une partie else dans un try...except...finally. La partie else sera exécutée quand il n’y a pas d’exception. Avec else, le bloc try...except devient:

try...except...else...finally

Par exemple:

try:
    found_person = persons['2']
except KeyError:
    print("Error finding")
    found_person = 'unknown'
else:
    print("Person found")
finally:
    print(found_person)

A l’exécution:

Person
found
Elon

Classe

On peut déclarer une classe en Python de cette façon:

class Student:
    pass

pass est un mot-clé valable pour les fonctions ou les classes pour dire de ne rien faire.

Instancier une classe

Pour instancier une classe:

student = Student()

Dans cet exemple:

  • studentest le nom de l’instance.
  • Student() est le nom de la classe.

Méthode membre

Une méthode membre peut être implémentée dans la classe de cette façon:

class Student:
    def add_student(self, name, student_id = 332):
        student = { "name": name, "student_id": student_id}
        students.append(student)

Une méthode membre doit avoir le 1er paramètre self lors de sa déclaration. Le mot-clé self peut aussi être utilisé pour désigner l’instance courante de la classe (équivalent de this).

Pour appeler une fonction membre, on peut utiliser les syntaxes suivantes qui sont équivalentes:
Si on instancie la classe de cette façon:
instance = Student()

  • instance.add_student('Alice', 34)
  • Student.add_student(instance, 'Alice', 34)

En Python 3, les classes n’héritant d’aucune classe héritent implicitement de la classe object.

Initializer

Un initializer est un espèce de constructeur. La différence avec un constructeur dans d’autres langages est que la classe est déjà construite quand l’initializer est exécuté.

L’initializer s’appelle toujours __init__() quelque soit le nom de la classe.

Par exemple:

class Student:
    def __init__(self, name, student_id = 332):
        student = {...}
        students.append(student)

Il ne peut y avoir qu’un seul initializer par classe.

L’initializer permet de déclarer et initialiser les données membres directement, par exemple si on écrit:

class Flight:
    def __init__(self, number):
        self._number = number

La variable _number n’a pas été déclarée avant. Cette seule déclaration suffit à déclarer la donnée membre _number.

En cas d’héritage:

  • Si l’initializer n’existe pas dans la classe fille, l’initializer de la classe parente est exécuté après instanciation de la classe fille.
  • Si un initializer existe dans la classe fille, l’initializer de la classe n’est pas implicitement exécuté. Il faut l’appeler explicitement avec super().__init__(<arguments>).

On peut utiliser la syntaxe du passage des paramètres par expansion pour éviter d’avoir à réécrire tous les arguments de l’initializer de la classe parente dans le constructeur de la classe fille.

Par exemple:

class Vehicule:
    def __init__(self, t_args, **d_args):
        ...

class Voiture(Vehicle):
    def __init__(self, nb_portes, t_args, **d_args):
        super(Voiture, self).__init__(t_args, **d_args)
        self.nb_portes = nb_portes

Attributs de classe et d’instance

En Python, on appelle:

  • Attributs de classe: des variables statiques d’une classe. Ces variables sont accessibles en utilisant la syntaxe <nom de la classe>.<nom variable>.
  • Attributs d’instances: des données membres d’un classe. Ces variables sont accessibles en utilisant la syntaxe <instance>.<nom variable> ou self.<nom variable>.

Par exemple:

class Example:
    variable = 5

print(Example.variable)   # 5 (attribut de classe)
Example.variable = 10
print(Example.variable)   # 10 (attribut de classe)

inst = Example()
print(inst.variable)      # 10 (valeur provenant de l'attribut de classe à l'initialisation)
inst.variable = 15        # modification de l'attribut d'instance
print(inst.variable)      # 15
print(Example.variable)   # 10 la variable de classe n'est pas modifiée.

Pour ajouter une variable membre à partir de l’initializer, par exemple:

class Student:
    def __init__(self, name, student_id = 332):
        self.name = name
        self.student_id = student_id
        students.append(self)

# self.<var> permet de définir une variable membre.

    def __str__(self):
        return "Student" + self.name

    def get_name_capitalize(self):
        return self.name.capitalize()

# capitalize() permet de remplacer la 1ère lettre par une majuscule.
Les attributs de classe sont partagés par toutes les instances

Si on affecte une valeur à un attribut de classe, l’attribut d’instance sera affecté s’il n’est pas initialisé dans l’initializer.

Par exemple, si on considère cette classe:

def Vehicle:
    couleur = 'blanc'

# Dans cette déclaration couleur correspond à un attribut de classe 
# et non à un attribut d'instance. Si on veut déclarer des attributs 
# de classe, il faut les initialiser dans l'initializer.

v1 = Vehicle()
v2 = Vehicle()
v1.couleur = 'rouge'      # Affectation de l'attribut d'instance
Vehicle.couleur = 'bleu'  # Affectation de l'attribut de classe
v3 = Vehicle()
v1.couleur rouge
v2.couleur bleu
v3.couleur bleu

Au moment de chercher la valeur d’un attribut, Python cherche dans cet ordre:

  • Existe-t-il un attribut d’instance ? Si oui c’est cette valeur qui est utilisée.
  • Existe-t-il un attribut de classe ? Si oui c’est cette valeur qui est utilisée.
  • Sinon une erreur est déclenchée

Il est possible d’affecter, de déclarer et d’initialiser des attributs à l’extérieur de la classe:

v1 = Vehicle()
v1.unknown = 6    # valide
# unknown est un attribut de classe

Définir une variable statique

Par exemple, si on considère la classe suivante:

class Student:
    school_name = "Springfield elementary"
    # Accesseur pour accéder à une variable membre

    def get_school_name(self):
        return self.school_name

On peut atteindre la variable statique sans instancier la classe:

print(Student.school_name)    # Pas d'instanciation

Héritage et polymorphisme

Dériver d’une classe

Pour dériver d’une classe, par exemple de la classe Student:

class HighSchoolStudent(Student):
    school_name = "Springfield High School"

La classe mère est Student.

En Python, l’héritage sert principalement pour éviter la duplication de code.

Surcharger une fonction

Pour surcharger une fonction, il n’y a pas de syntaxe particulière:

class HighSchoolStudent(Student):
    # Surcharge, pas de mot clé particulier
    def get_school_name(self) 
        return "This is the high school"

Accéder à une fonction dans la classe parente

On peut accéder à une fonction de la classe parente en utilisant super():

class HighSchoolStudent(Student):
    ...

    def get_name_capitalize(self):
        original_value = super().get_name_capitalize()
            return original_value + "HS"

super() est le mot clé pour atteindre la fonction de la classe parente. Il existe d’autres possibilités pour appeler la méthode de la classe parente.

Si on instance la classe fille:

highSchoolStudent = HighSchoolStudent()

alors:

  • super().get_name_capitalize() (syntaxe à privilégier) ou
  • Student.get_name_capitalize(highSchoolStudent) ou
  • super(HighSchoolStudent, self).get_name_capitalize()
Pas de modificateurs de portée

En Python, il n’existe pas de modificateurs de portée (private, protected), tout est publique.

Toutefois il existe des conventions:

  • Préfixe __ pour indiquer qu’une méthode ou un attribut est privé.
  • Préfixe _ pour indiquer qu’une méthode ou un attribut est protected.

On peut surcharger des méthodes particulières comme par exemple __str__ qui permet de convertir une instance d’une classe en chaîne de caractères. Pour surcharger cette fonction, il peut écrire dans la classe:

def __str__(self):
    return "Student"

Quelques autres méthodes particulière:

  • __bool__: permet de savoir si un objet est évalué comme valant True ou False dans une expression booléenne.
  • __del__: il s’agit du destructeur. Cette méthode est appelée quand l’objet est détruit en exécutant:
    del <instance>

    Ou:

    <instance> = None
    
  • __add__, __mul__, __sub__: permettent d’implémenter des comportements lorsque les opérateurs +, * et - sont utilisés entre 2 objets.

Héritage multiple

L’héritage multiple est possible en Python, il suffit d’utiliser la syntaxe:

class <nom classe>(<parent 1>, <parent 2>, ..., <parent n>)

En Python, le polymorphisme s’implique en ayant des classes avec les mêmes interfaces (au sens signature des méthodes car la notion d’interface n’existe pas en Python).

Lecture et écriture de fichiers

Ecrire un fichier

On peut utiliser les fonctions open(), write() et close() pour respectivement ouvrir, écrire et fermer un fichier sur le disque, par exemple:

def save_file(student):
    try:
        f = open("student.txt", "a")      # D'autres options sont possibles
        f.write("student.txt" + "\n")     #  permet d'écrire une ligne
        f.close()
    except Exception:
        print("Could not save file")

Les autres options possibles lors de l’ouverture de fichier:

  • "w": writing; écrase le fichier
  • "r": reading pour lire (valeur par défaut)
  • "x": création exclusive. Si le fichier existe déjà, une erreur est générée.
  • "rb": reading as binary
  • "wb": writing as binary
  • "a": append
  • "b": binary mode
  • "t": text mode
  • "+": ouvre un fichier pour mise à jour (lecture ou écriture)

Par défaut, l’encodage des fichiers texte en Python est fait en fonction du résultat de la fonction:

import sys
sys.getdefaultencoding()     # équivalent UTF-8

f = open('fileName.txt', mode = 'wt', encoding='utf-8')

Ainsi:

  • La partie encoding='utf-8' est optionnel.
  • mode='wt' correspond au mode write + text mode.
Pas de writeLine()

Il faut rajouter explicitement /n pour les retours à la ligne:

f.write("<chaine de caractères à rajouter>")

f.write("<chaine de caractères à rajouter>/n")   # avec le retour à la ligne.

/n pour le retour à la ligne peut être utilisé quelque soit l’OS. Pour Windows /n est remplacé par les bons caractères.

A la fin, il faut fermer en exécutant:

f.close()

Lire un fichier

Pour lire un fichier

def read_file():
    try:
        f = open("students.txt", "r")
        for student in f.readlines():
            add_student(student)
        f.close()
    except Exception:
        print("Could not read file")

Quelques fonctions pour lire le contenu d’un fichier après l’avoir ouvert:

g = open('wasteland.txt', mode='rt', encoding='utf-8')
  • g.read() permet de lire tout le fichier d’un coup.
  • g.seek(0) permet de placer le curseur à un certain point du fichier (0 signifie au début)
  • g.readline() lecture d’une ligne du fichier. La dernier caractère de la chaine contiendra \n le cas échéant (ce caractère peut ne pas être présent).
  • g.readlines() lit toutes les lignes d’un fichier et les range dans une liste.
  • g.close() ferme le fichier.

Utiliser des iterators

Lors de la lecture d’un fichier, on peut utiliser un iterator de cette façon:

f = open(...)
for line in f:
    print(line)
    # On peut aussi utiliser la syntaxe:
    sys.stout.write(line)
    f.close()

Pour utiliser sys.stout.write(line), il faut effectuer un import:

import sys

Ecrire à la suite d’un fichier texte

On utilise la syntaxe suivante:

f = open('wasteland.txt', mode='at', encoding='utf-8')

'at' pour append + text mode

Pour écrire des chaines ligne par ligne:

f.writelines(<liste contenant les chaînes de caractères>)

Il faut indiquer /n explicitement si on veut retourner à la ligne.

Ecrire un fichier binaire

f.tell() permet d’indiquer l’offset par rapport au début du fichier.

Pour écrire des bytes:

f.write(byte(...))
f.write(b'...')

Quelques générateurs de transformation vers les bytes.

Pour un entier (32 bits) vers des bytes:

  • i & 0xff ⇒ conversion du 1er octet de l’entier.
    i est un entier
    0xff correspond à 255
  • i >> 8 & 0xff ⇒ conversion du 2e octet de l’entier
    >> 8 permet de déplacer le curseur de 8 bits (1 octet) vers la droite
  • i >> 16 & 0xff ⇒ conversion du 3e octet de l’entier

Utiliser un bloc try…finally

L’utilisation d’un bloc try...finally lors de la lecture d’un fichier permet de bien fermer le fichier après lecture même dans le cas où une erreur survient, par exemple:

try:
    f = open(...)
    ...
finally:
    f.close()

Considérer un contexte de lecture avec des “with blocks”

Les “with blocks” permettent d’éviter d’avoir à exécuter f.close() à la fin des ouvertures de fichier.

Par exemple:

def read_lines(filename):
    with open(...) as f:
        return [int(line.strip()) for line in f]

Ce bloc est équivalent à using en C#.

Pour exécuter du code Python en ligne: https://colab.research.google.com.

7 responses... add one

Merci pour l’aide-mémoire! Vraiment sympa!
Avez-vous un aide-mémoire de ce type pour C#10 svp?
Merci

Leave a Reply