Introduction à la programmation en Python

.

Objectifs

  • Présentation du langage, programmation objet en Python
  • Introduction à quelques spécificités intéressantes
  • Présentation de quelques outils et bibliothèques scientifiques
  • Donner envie ! (tant qu'on y est)

Motivations

Pourquoi python ? Productivité + performances

  • un langage devenu très populaire dans la communauté scientifique (et au-delà)
  • langage de haut niveau: permet un développement rapide
    • syntaxe simple
    • gestion de la mémoire
    • librairie standard importante
  • autres avantages
    • portable / multi-plateformes
    • permet d'utiliser des librairies dans d'autres langages facilement

Caractéristiques de base

  • langage interprété (avec bytecode)
  • gestion mémoire transparente (ou presque)
  • typage implicite
  • typage dynamique
  • orienté objet (pas nécessaire mais utile)

Types élémentaires


  • entiers (integer) 1, -3
  • nombre à virgule flottante (float) 2.0, 5.45
  • chaines de caractères (str) "hello" ou 'hello'
  • booléen (bool) True False

On dispose des opérateurs classiques sur ces types de données.

Typage


le typage est implicite en python :

In [162]:
a = 0
b = "hello"
c = 6.789
In [163]:
print(c) 
6.789
In [164]:
print(a,b,c,end=".")
0 hello 6.789.
In [165]:
print(type(a))
a = a + c
print(a)
print(type(a)) 
<class 'int'>
6.789
<class 'float'>
In [166]:
# petite subtilité
print(10/2)
print(10//2)
5.0
5

Structures de contrôles

Les structures imbriquées sont indiquées par des tabulations

In [167]:
b = 2
a = 12 + b

if a>b: 
    print(a)
else:
    print(b)   
14

Boucles

In [168]:
a = 5
while a>0:
    a = a - 1
    print(a,end=" ")
4 3 2 1 0 
In [169]:
for i in range(10):
    print(i,end=" ")
0 1 2 3 4 5 6 7 8 9 

"for" itère sur une séquence de valeurs

Fonctions

le corps des fonctions est indiqué par des tabulations

In [170]:
# arguments non typés  
def max(a,b):
    if a>b:
        return a
    else:
        return b

print(max(5,4)) 
print(max("abc","defg"))
5
defg
In [171]:
# typage contraint par les opérateurs
print(max("hello",34))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-171-859b28c3584e> in <module>()
      1 # typage contraint par les opérateurs
----> 2 print(max("hello",34))

<ipython-input-170-7291f7ade40d> in max(a, b)
      1 # arguments non typés
      2 def max(a,b):
----> 3     if a>b:
      4         return a
      5     else:

TypeError: unorderable types: str() > int()

Types structurés natifs

En plus des types de base, python peut manipuler directement les types de données structurés suivants :

  • tuples
  • listes
  • tables associatives (appelées dictionnaires)

Dans tous les cas, on accède aux éléments avec un index, signalé entre []

Dans le cas des listes et tuples, c'est un entier donnant la position (commence à 0)

Dans le cas des tables, l'index fait partie de la définition de la table

In [ ]:
# tuple ... on peut mélanger les types
t=(1,3,4,"hello")
print(t[2])
In [ ]:
(1,2,3)+(4,5)
In [ ]:
# ne peut être modifié "immutable"
t[0] = 0
In [ ]:
# listes ... on peut mélanger les types, mais cela n'a pas trop de sens
l=[1,2,3,4,"hello"]
print (l[1])
In [ ]:
[1,2,3]+[4,5]
In [ ]:
# mutable
l[4] = 6
l

Indexation des séquences

valable pour tuple, liste, et chaines de caractères

In [ ]:
# indexation négatives depuis la fin
l[-1]
In [ ]:
# "tranches": deuxième borne non incluse
print(l[2:5])
print (l[:2])
print (l[1:-2])
print (l[3:4])
print (l[:3]+l[3:])
In [ ]:
# avec des sauts
print(l[::2]) 
print(l[::-1])

La variable liste est en fait un pointeur

In [ ]:
l = [1,2]
a = l
a[0]=8
print(a)
print(l)

Mais les sous-listes sont copiées:

In [ ]:
l = [1,2,3]
a = l[:]
b = l[::-1]
a[0] = 8
b[0] = -2
l

Dictionnaires (Tables associatives)

In [ ]:
# 
d = {"tim":12, "bob":35, "al":27}
print(d["tim"])

Les entrées sont créées dynamiquement :

In [ ]:
d["rt"] = 0
print(d)

Tout type immuable peut servir d'index (cela exclut donc listes et dictionnaires)

In [ ]:
d2 = {1:3, 4:5, 1:123}
d3 = {(1,2):"?",(4,5):"."}
# seule contrainte: index immutable

Structures vides

In [ ]:
()  # ---> tuple
[]  # ---> liste
{}  # ---> dictionnaire

(2,) # tuple à un seul élément

Itérations

Les types structurés sont prévus pour être l'objet d'itérations

In [ ]:
for x in [1,2,3,4,5]: 
    print(x,end=" ")
print()   
for x in ("a",3,-1.5):
    print(x,end=" ")
In [ ]:
for x in {1:2,10:3,100:45}:
    print(x,end=" ")
In [ ]:
d = {1:2,10:3, 100:45}
for cle in d:
    print(d[cle],end=" ")

Opérations sur les types structurés

In [ ]:
#affectation : on peut décomposer
(a,b,c) = (1,2,3)
print(a,b)

# moins utile mais possible
l = [1,2,3]
[a,b,c] = l

# seulement en python 3
a, *r = l
print(r)
In [ ]:
# dans les itérations
l = [(1,2),(1,3),(4,5)]
for (i,j) in l:
    print(i+2*j,end=" ")
In [ ]:
# application : échange de variables
(a,b) = (b,a)

Quelques méthodes et opérateurs utiles


Les listes sont des objets, avec les méthodes suivantes :

  • append: ajoute un élément en fin de liste
  • extend: ajoute une liste à la fin d'une liste (cf aussi opérateur +)
  • sort: trie sur place par ordre croissant, si les valeurs sont comparables
  • reverse: renverse sur place

cf la documentation python pour les listes

In [ ]:
l = [1,2]
l.append(10)
l.extend([3,4])
print(l+[4,5])
print(len(l))
l.append(4)
print(l)

Pour les dictionnaires:

  • keys: liste des "clefs" ou valeurs de l'index de la table
  • values: liste des valeurs
  • items: listes des couples (clef,valeur)
  • get: récupère une valeur avec la clef, ou une valeur par défaut
  • initialisation à partir de couples: dict

cf la documentation python pour les dict

Pour les chaines:

  • "+" (opérateur) pour concaténer 2 chaines
  • join pour concaténer une liste de chaine avec un séparateur
  • split pour diviser une chaine selon un séparateur

cf la documentation python pour les chaines

Pour tous:

  • len: fonction donnant le nombre d'éléments
  • in: opérateur (infixe), teste l'appartenance (dans le cas d'un dictionnaire, teste la présence d'une clef)

Pour les dictionnaires:

  • del d[clef] supprime une entrée
In [ ]:
d = {"tim":12, "bob":35, "al":27}
print (d)
print (d.keys())
print (d.values())
print (d.items())
print (len(d))
print (d.get('al',0))
print (d.get('d',0))
del d['al']
print (d)
print (d.get("y",0))
print (dict([("a",1),("b",3)]))
print ('x' in d)
In [ ]:
print("hello"+"world")
print("hello world !".split())
print(" ".join(["hello","world","!"]))

Itérations : alternatives

In [ ]:
# iterations abrégées
[x**2 for x in range(10)]

list1 = [1,2,3,9,100]
list2 = [x**2 for x in list1]
print(list1)
print(list2)
In [ ]:
# itération avec l'indice: 
for (i,x) in enumerate(range(100,110)):
    print(i,"->",x,end=" ") 
In [ ]:
# itération abrégée des dictionnaires: 
d = {1:2,3:4,5:9}
d2 = {x:d[x]**2 for x in d}
d2

Retour sur les chaines de caractères

les chaines sont des séquences

In [ ]:
print("hello"[3])
print("hello"[2:5])

Il n'y a pas de type caractères: une chaine de longueur 1 est une chaine

In [ ]:
print(type("hello"))
print(type("hello"[0]))
In [ ]:
#plusieurs façons de décrire une chaine
"blabla"
' ou blabla '

# guillemets triples pour étaler sur plusieurs lignes

""" encore plus 
de blabla"""
In [ ]:
#Quelques méthodes
a = "Hello  world !"# deux espaces entre hello et world
print(a.upper())
print (a.lower())
print (a.replace(" ","---"))
items = a.split()
print (items)
print ("---".join(items))
print (a.split("l"))

Pour gérer les fichiers, on doit gérer l'ouverture d'un canal:

In [ ]:
f=open("monfichier.txt","w") # écriture
f.write("blabla\n")
f.write("bla")
f.close()
In [ ]:
f=open("monfichier.txt") # lecture 
print(f.read())
f.close()
f=open("monfichier.txt") # lecture 
print(f.readlines())
f.close()

Importer des modules

In [ ]:
import math
math.exp(1)
In [ ]:
# alternativement
from math import exp
from math import *
exp(1)
In [ ]:
import math as fct_math
print(fct_math.exp(1))
from math import log as log2
print(log2(1))

Programmation fonctionnelle

Python peut manipuler les fonctions comme des objets, et un certain nombre de fonctions sur les fonctions sont utiles:

  • map : permet d'appliquer une fonction à tous les éléments d'une séquence
  • filter: filtre une collection avec une fonction booléenne

Exemples :

In [ ]:
from math import sin
for x in map(sin,[0,0.5,1]):
    print(x,end=" ")
In [ ]:
def pos(x):
    return x>0

for x in filter(pos,[-2,1,3,-6]):
    print(x,end=" ")

Il est souvent plus facile avec ces fonctions de traiter l'argument fonction de façon "anonyme", en définissant un objet fonction jetable comme suit:

In [ ]:
for x in filter(lambda u:u>0,[-2,1,3,-6]):
    print(x,end=" ")
    

Paramètres de fonctions

le format vu jusqu'ici pour les fonctions est en fait un schéma de base avec des arguments fixes:

def nom_fonction(argument1,arg2,...,argn):

On peut aussi mettre des arguments optionnels de la façon suivante :

def nom_fonction(arg1,...,argn, option1 = defaut1, option2 = defaut2, ...)

In [ ]:
def message(texte, signature, politesse = "cordialement"):
    print(texte)
    print(politesse+"," )
    print(signature)
    
message("blabla","François") 
In [ ]:
message("blabla","François Hollande",politesse = "sincerement")
In [ ]:
# on peut avoir un nombre d'argument variable en passant un pointeur sur toute la séquence des arguments
def variable(*tous_les_args):
    for x in tous_les_args:
        print(x,end=" ")
        

variable(3,"+",4,"=",3+4)

 
In [ ]:
# on peut même récupérer les options
def illisible(*args,**options):
    print(args)
    print(options)
    
    
illisible("euh","comment","dire",point=23,extra=2,bonus="craquage")
In [ ]:
# on peut définir des fonctions de fonctions
def deux_f(f):
    def fbis(x):
        return 2*f(x)
    return fbis

from math import sin

print(deux_f(sin)(0.5))

#avec une lambda
def deux_f(f):
    return lambda x: 2*f(x)

print(deux_f(sin))

Passage des paramètres

En python, les fonctions passent implicitement des valeurs pour les types de base (entiers, flottants, booléens), et on ne peut donc les modifier.

Les autres types, notamment les types complexes, sont passés par référence, et donc sont modifiables. Mais on peut quand même passer des valeurs, qui sont de façon transparente assignées en mémoire à une variable locale.

In [ ]:
def test2(l):
    l[0]=0
    return l
    
l1 = [1,2,3,4]
print(test2(l1))
print(l1)
print(test2([1,2]))

Types structurés (suite): les ensembles

Le type 'set' modélise l'équivalent d'un ensemble en mathématique: une collection d'items sans ordre spécifique.

In [ ]:
# initialisation et ajouts

a = set([1,3,4,5,1,1,5])
b = set()
b.add(4)
b.add(6)

print(a)
print(b)
In [ ]:
# appartenance, intersection, union
x = 3

print(x in b)
print(a,b)
print(a & b)
print(a | b)
In [ ]:
# cardinal, inclusion , différence
print(len(a))
print(set([1,3]) <= a)
print(a - b)
print(b - a)
In [ ]:
# suppression
a.add(4)
a.remove(4)
b.clear()

print(a,b)
a.add(4)

Exceptions

Python définit un ensemble d'exceptions, qui sont un type à part et qui peuvent être aussi étendues, et bien sûr levées et rattrapées.

Quelques exemples:

In [ ]:
a = 0.
b = 15
print(b/a)
In [ ]:
l = []
l[0]
In [ ]:
a = 0.5
b = 15
try: 
    print(b/a)
except ZeroDivisionError:
    print(0)
    
l = []
try:
    print(l[0])
except IndexError:
    print("?")
In [ ]:
l = []
try:
    print(l[0])
except IndexError:
    print("oops")
    raise
except: 
    print("erreur inattendue")
    raise
finally:
    l.append(1)
    print(l)
In [ ]:
l = []
try:    # passage risqué
    print(l[0])
except IndexError:  # raté !
    print("oops")
else: # si c'est bon on s'assure de ça (peut lever une erreur aussi)
    print(1/l[0])
finally: # dans tous les cas on veut faire ça
    l.append(1)
    print(l)