Elle se compose de modules, que l'on doit importer (mot clé import).
À explorer : https://docs.python.org/3/py-modindex.html
Il y a de nombreux paquetages http://pypi.python.org/pypi (environ 120000) pour à peu près n'importe quoi ...
import sys
sys.stdout.write('Hello!\n') # équivalent de 'print (Hello!)'
sys.stderr.write('Raté !')
sys.getfilesystemencoding()
sys.getrecursionlimit()
sys.platform
import os
os.listdir('toto') # appelle 'ls' sous linux, et 'dir' sous windows
!ls -l toto
os.environ['HOME'] # variables d'environnement (dictionnaire)
print (os.environ.keys())
Une des fonctions les plus utiles est os.walk (parcours récursif d'une arborescence):
tree = os.walk('toto')
print (tree)
list(tree)
ls -R toto
On voit que os.walk retourne une liste de triplets de la forme
(repertoire, [liste de repertoires], [liste de fichiers])
Pour savoir si un chemin représente un répertoire, un fichier ou un lien symbolique, on utilise os.path:
os.path.islink('/home/jyt/ens/M1_python/toto')
os.path.islink('/home/jyt/ens/M1_python/toto/link')
os.stat('/home/jyt/ens/M1_python/toto')
oct(_.st_mode) # les 3 derniers chiffres en octal correpondent aux permissions
os.rename('toto','baz')
!ls baz
os.system('echo "blablabla">baz/blu') # execute une commande ou un programme
cat baz/blu
import pickle
f = open('/tmp/toto','wb') # sauvegarde
pickle.dump(47,f)
pickle.dump(sys.argv,f)
pickle.dump({'canard':'coin', 'chat':'miaou'},f)
f.close()
f = open('/tmp/toto','rb') # rechargement
try:
while 1: print (pickle.load(f))
except EOFError: pass
import shelve
d = shelve.open('titi')
d['ga'] = (1,2)
d['bu']= 'abracadabra'
d['zo'] = 3.1415926535
d['meu'] = {'a':'b'}
d.close()
d = shelve.open('titi')
print (d.keys())
print(list(d.keys()))
['ga', 'bu', 'zo', 'meu']
d['bu'] = list(range(3))
d['bu'].append(8)
print (d['bu']) # Un piège : l'attribut "writeback" est False par defaut
Module re
re.match(regex, chaîne) et re.search(regex, chaîne) retournent (en cas de match) un objet de type Match.
re.match ne cherche qu'au début de la chaîne.
re.search cherche à n'importe quelle position.
re.findall retourne une liste de toutes les occurences trouvées.
Recherches multiples : re.compile construit un automate.
Pratique : les raw strings
"abc\n\1def"
print(_)
r"abc\n\1def"
print ("abc\n\1def")
print (r"abc\n\1def")
Dans une chaîne ordinaire, \n est interprété comme un retour ligne. Pour avoir le caractère '\' suivi du caracère 'n', il faut faire précéder le '\' d'un autre '\', qui est le caractère d'échappement.
Les rawstrings interprètent '\' comme un caractère ordinaire.
import re
m = re.search(r't(.)t\1', 'le tutu') # le \ a une signification spéciale dans les regexp
m
On verra plus loin la signification de cet exemple.
Le parseur python interprète le caractère '\' dans une chaîne comme un caractère d'échappement indiquant le début du nom d'un caractère spécial, comme \n,\t,\x2a,\54,....
Le module re utilise aussi '\' comme caractère d'échappement pour modifier la signification des caractères spéciaux comme *, ., $, ^.
La calamité est qu'on doit parfois échapper le caractère d'échappement (quand le caractère spécial est reconnu par les deux parseurs) et parfois non (quand le caractère spécial n'est pas reconnu par le parseur python).
Dans une raw string, le parseur python ne substituera pas les caractères spéciaux à leur encodage.
Les expressions régulières sont puissantes, mais d'un maniement délicat. Le petit test suivant (emprunté au livre de Pilgrim) permet de visualiser leur effet :
def re_show(pat, s):
print (re.compile(pat, re.M).sub("{\g<0>}", s.rstrip()),'\n')
La compilation avec l'option re.M permet de traiter une chaîne contenant des sauts de lignes.
L'expression $\tt\backslash g<0>$ représente la chaîne matchée, qui sera donc substituée à elle même, entourée d'accolades.
s = "le cheval de mon cousin ne mange du foin que le dimanche"
p = r'(\b\w*in\b)' # un "bord" (\b) suivi de 0 ou plusieurs caractères alphabétiques (\w*),
#des lettres i et n, et d'un bord
re_show(p,s)
t = """ceci \\ est un \\backslash
mais cela \\n n'est pas un \n saut de ligne"""
t
print (t)
re_show('\n|\\\\',t)
re_show(r'\n|\\',t) # autre version
Ce sont les suivants
. ^ $ * + ? { } [ ] \ | ( ) \A \Z \b \B
"[" et "]" spécifient une classe de caractères :
[abcd] ou [a-d].
Les métacaractères ne sont pas actifs dans une classe : [a-c(?\$] contient les caractères a,b,c,(,?,$.
Un ^ au début définit le complémentaire : les caractères non alphanumériques sont
[^a-zA-Z0-9].
Le \ est le caractère d'échappement.
re_show(r'[au\n\\]',t)
\d équivalent à [0-9]. \D équivalent à [^0-9]. \s équivalent à [ \t\n\r\f\v]. \S équivalent à [^ \t\n\r\f\v]. \w équivalent à alphanumérique (ou seulement [a-zA-Z0-9\_] si ascii). \W est les complémntaireCes séquences peuvent être incluses dans une classe. Par exemple,
[\s\\\\] contient tous les blancs et le backslash
re_show(r'[\s\\]',t)
Le métacaractère "." matche tout sauf le saut de ligne.
Il existe un mode re.DOTALL où il matche tout caractère.
Le "?" matche 0 ou 1 fois.
L'étoile <regexp>*
signifie 0 ou plusieurs fois <regexp>
.
Le plus <regexp>+
signifie 1 ou plusieurs fois <regexp>
.
Les accolades <regexp>{m,n}
signifient au moins $m$ fois
et au plus $n$ fois (<regexp>{n}
pour exactement $n$ fois).
re_show(r'o*', 'boogie-woogie')
re_show(r'o+', 'boogie-woogie')
re_show(r'o{2,4}', 'oh boogie-woogie wooof woooof wooooof')
La barre verticale <R1>|<R2>
matche <R1>
ou <R2>
. On peut
mettre une expression entre parenthèses pour lui appliquer un
opérateur comme $*$ ou $+$
s = 'baaababaaababbaababbbabaabaabbbaaaababa'
re_show(r'(a|ba)+',s)
Les parenthèses servent aussi à indiquer des groupes (voir plus loin).
^ et \$ marquent respectivement le début et la fin d'une ligne.
re_show('^m|sh$|^\s',t)
Les expressions régulières, jusqu'ici données sous forme de chaînes, peuvent être compilées. Le résultat est une instance de la classe RegexObject.
Le module re exporte des fonctions ayant les mêmes noms que les méthodes des RegexObjects :
s='baaababaaababbaababbbabaabaabbbaaaababa'
r = re.compile('((a|ba)+)')
r.findall(s)
A cause des deux paires de parenthèses, findall voit deux groupes, et retourne une liste de couples.
Pour n'avoir que ce qu'on veut, on peut utiliser un groupe non-capturant
r = re.compile('(?:a|ba)+') # noter le ?: au début de la parenthèse interne
r.findall(s)
Les méthodes r.match (matche au début de la chaîne) et r.search (matche n'importe où dans la chaîne) retournent un MatchObject ou None.
La méthode r.findall trouve toutes les occurences (non recouvrantes) de l'expression et retourne une liste. r.finditer retourne un itérateur.
r.split casse la chaîne selon les occurences de r.
r.sub et r.subn remplacent les occurences de r par une chaîne fixée, ou leur appliquent une fonction.
print (re.split(r'\s|\\',t))
re.sub(r'\s+|\\', ' ',t)
Les méthodes les plus importantes d'un MatchObject m sont :
s='baaababaaababbaababbbabaabaabbbaaaababa'
p = re.compile('((a|ba)+)')
m = p.match(s)
print (m.group())
print (m.span())
m
p.sub(lambda x:'.'*len(x.group(0)),s)
p = re.compile('(a(b)c)d(e)') # groupes emboîtés
m = p.match('abcde')
m.groups()
('abc', 'b', 'e')
print (m.group(0))
print (m.group(1))
print (m.group(2))
print (m.group(3))
Évitent que les parenthèses (obligatoires pour encadrer le +) ne soient interprétées comme un groupe devant capturer la première occurence d'une des lettres i,j,a,b,c :
m = re.match('([ij]|[abc])+','aabbjjiijcxabb')
print (m.groups())
print (m.group(0))
print (m.group(1))
m = re.match('(?:[ij]|[abc])+','aabbcxabb')
print (m.group(0))
m.groups()
Les groupes peuvent être nommés en utilisant la syntaxe $\tt(?P<name>...)$
s="le cheval de mon cousin ne mange du foin que le dimanche"
p = re.compile(r'(\b\w*in\b).*(\bd\w*e\b)')
m = p.search(s)
m.groups()
p = re.compile(r'(?P<qui>\b\w*in\b).*(?P<quand>\bd\w*e\b)')
m = p.search(s)
m.group('quand')
Syntaxe : r.sub(f, s, count=0)
où f accepte comme argument un MatchObject et retourne la chaîne à utiliser en remplacement du match.
s='''<html>
<body>
<H1>Mon beau cours de python</H1>
<H2>Blabla</H2> et ri et
ra patati et patata
</body>
</html>'''
def capitalize(m):
return '<h1>'+ ' '.join([w.capitalize() for w in m.group(1).split()])+'</h1>'
p=re.compile(r'<h1>(.*?)</h1>', re.I|re.M)
print (re.findall(p,s))
print (p.sub(capitalize,s))
On peut utiliser cette technique pour l'exercice 3 du TD 2 :
import random, re
p = re.compile('(\w)(\w\w+)(\w)', re.M) # reconnaît les mots d'au moins 4 lettres
def touille(m): # m est un match object, m.group(2) est le contenu de (\w\w+)
milieu = list(m.group(2)) # donc le milieu de la chaîne matchée, qu'il faut convertir en
random.shuffle(milieu) # liste et nommer pour la mélanger en place
return m.group(1) + ''.join(milieu) + m.group(3) # on remet la première et la dernière lettre en place
def blurr(s):
return p.sub(touille,s)
blurr('Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents')
Les opérateurs non-gloutons *?, +?, ??, ou {m,n}?, matchent aussi peu de texte que possible
s = '<html><head><title>Title</title>'
print (re.match('<.*>', s).group())
print (re.match('<.*?>', s).group())
Les options de compilation sont aussi utilisables avec les chaînes (syntaxe (?iLmsux)). En mode verbeux, les espaces sont ignorés et # signale un commentaire :
pat_url = re.compile( r'''(?x) # flag à mettre au début depuis 3.6
( # verbose identify URLs within text
(http|ftp|gopher) # make sure we find a resource type
:// # needs to be followed by ://
(\w+[:.]?){2,} # at least two domain groups: (gouv.)(fr)
(/?| # just the domain name (maybe no /)
[^ \n\r"]+ # or stuff then space, newline, tab, quote
[\w/]) # resource name ends in alphanumeric or /
(?=[\s\.,>)'"\]]) # assert: followed by white or clause ending
) # end of match group''')