La bibliothèque

Elle se compose de modules, que l'on doit importer (mot clé import).

À explorer : https://docs.python.org/2.7/library/

  • sys : sys.argv, sys.stdin... principalement
  • os : fonctions "bas niveau" (appels système)
  • re : expressions régulières
  • math : flottants, complexes
  • pickle, cPickle : (dé)sérialisation
  • shelve : dictionnaires persistants
  • urllib, urllib2 : http
  • HTMLParser : comme son nom l'indique ...
  • xmlrpclib : "Remote Procedure Call" (web services)
  • cgi : Common Gateway Interface
  • sqlite3 : Bases de données SQLite
  • socket : réseau bas niveau

Il y a de nombreux paquetages http://pypi.python.org/pypi (environ 120000) pour à peu près n'importe quoi ...

Entrées/sorties : le module sys

Exporte en particulier sys.stdin, sys.stdout, sys.stderr, sys.argv, sys.exit.

In [1]:
import sys
sys.stdout.write('Hello!\n') # équivalent de 'print Hello!'
Hello!

In [2]:
sys.stderr.write('Raté !')
Raté !
In [3]:
sys.getfilesystemencoding()
Out[3]:
'UTF-8'
In [4]:
sys.getrecursionlimit()
Out[4]:
1000
In [5]:
sys.platform
Out[5]:
'linux2'

Appels système : os et os.path

Permet d'utiliser le système de manière indépendante de la plateforme.

In [7]:
import os
os.listdir('toto') # appelle 'ls' sous linux, et 'dir' sous windows
Out[7]:
['5', 'bar', 'titi', 'bla', 'blu', 'a.py~', 'foo']
In [8]:
os.environ['HOME'] # variables d'environnement (dictionnaire)
Out[8]:
'/home/jyt'
In [9]:
print os.environ.keys()
['LESS', 'KDE_COLOR_DEBUG', 'PYTHONDONTWRITEBYTECODE', 'LC_PAPER', 'XCURSOR_THEME', 'KDE_FULL_SESSION', 'META_CLASS', 'PROFILEHOME', 'SHELL', 'COLORFGBG', 'XDG_DATA_DIRS', 'HISTSIZE', 'XMODIFIERS', 'LC_ADDRESS', 'PYTHONPATH', 'KONSOLE_DBUS_SERVICE', 'LC_CTYPE', 'GS_LIB', 'SHELL_SESSION_ID', 'SCREENDIR', 'DESKTOP_SESSION', 'GTK_MODULES', 'HOSTNAME', 'LC_SOURCED', 'LC_NAME', 'XDG_MENU_PREFIX', 'LS_COLORS', 'SSH_ASKPASS', 'LC_NUMERIC', 'QTEST_COLORED', 'LESSOPEN', 'USER', 'INPUTRC', 'QTDIR', 'DM_CONTROL', 'PS1', 'LANGUAGE', 'SESSION_MANAGER', 'SHLVL', 'WINDOWID', 'EDITOR', 'KONSOLE_DBUS_SESSION', 'QT_PLUGIN_PATH', 'GTK2_RC_FILES', 'MAIL', 'PKG_CONFIG_PATH', 'TMPDIR', 'BROWSER', 'DBUS_SESSION_BUS_ADDRESS', 'LC_IDENTIFICATION', 'XDG_CONFIG_DIRS', 'LC_MONETARY', 'GTK_RC_FILES', 'LC_TIME', 'PAGER', 'KDE_MULTIHEAD', 'TMP', 'HOME', 'DISPLAY', 'LANG', 'QTLIB', 'QTINC', '_', 'CANBERRA_DRIVER', 'G_FILENAME_ENCODING', 'GIT_PAGER', 'NLSPATH', 'LC_MESSAGES', 'LC_COLLATE', 'LOGNAME', 'PATH', 'KDE_SESSION_VERSION', 'TERM', 'XDG_SESSION_COOKIE', 'GCONF_TMPDIR', 'WINDOWPATH', 'QT4DOCDIR', 'KDE_SESSION_UID', 'XDM_MANAGED', 'QT_XFT', 'LESSCHARSET', 'LESSKEY', 'MDV_MENU_STYLE', 'PYTHONSTARTUP', 'OLDPWD', 'LC_TELEPHONE', 'CLICOLOR', 'HISTCONTROL', 'LC_MEASUREMENT', 'PWD']

Une des fonctions les plus utiles est os.walk (parcours récursif d'une arborescence):

In [10]:
tree = os.walk('toto')
print tree
<generator object walk at 0x24127d0>

In [11]:
list(tree)
Out[11]:
[('toto', ['bar', 'foo'], ['5', 'titi', 'bla', 'blu', 'a.py~']),
 ('toto/bar', [], ['3', '2', 'link', '1']),
 ('toto/foo', [], [])]
In [12]:
ls -R toto
toto:
5  a.py~  bar/  bla  blu  foo/  titi

toto/bar:
1  2  3  link@

toto/foo:

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:

In [13]:
os.path.islink('/home/jyt/python/exos/toto')
Out[13]:
False
In [14]:
os.path.islink('/home/jyt/python/exos/toto/bar/link')
Out[14]:
True
In [15]:
os.stat('/home/jyt/python/exos/toto')
Out[15]:
posix.stat_result(st_mode=16877, st_ino=516130, st_dev=2057L, st_nlink=4, st_uid=10001, st_gid=4, st_size=4096, st_atime=1506090798, st_mtime=1506084717, st_ctime=1506090787)
In [16]:
os.rename('toto','baz')
In [17]:
ls baz
5  a.py~  bar/  bla  blu  foo/  titi

In [18]:
os.system('echo "blablabla">baz/blu') # execute une commande ou un programme
Out[18]:
0
In [19]:
cat baz/blu
blablabla

Pickle : mise en bocal

Pour sauvegarder des données au format Python.

Surtout utile pour sauvegarder des sessions.

On ne peut pas tout sauver (sockets, modules...).

In [20]:
import pickle
f = open('/tmp/toto','w') # sauvegarde 
pickle.dump(47,f)
pickle.dump(sys.argv,f)
pickle.dump({'canard':'coin', 'chat':'miaou'},f)
f = open('/tmp/toto')     # rechargement 
try:
    while 1: print pickle.load(f)
except EOFError: pass
47
['-c', '-f', '/home/jyt/.ipython/profile_default/security/kernel-796e8aa1-90da-4c29-80ab-71d361446c74.json', "--IPKernelApp.parent_appname='ipython-notebook'", '--profile-dir', '/home/jyt/.ipython/profile_default', '--parent=1']
{'canard': 'coin', 'chat': 'miaou'}

Shelve

Table de hachage stockée sur disque.

Restrictions : les mêmes que pour pickle. De plus, les clés doivent être des chaînes.

In [21]:
import shelve
d = shelve.open('titi')
d['ga'] = (1,2)
d['bu']= 'abracadabra'
d['zo'] = 3.1415926535
d['meu'] = {'a':'b'}
d.close()
In [22]:
d = shelve.open('titi')
print d.keys()
['ga', 'bu', 'zo', 'meu']
d['bu'] = range(3)
d['bu'].append(8)
print d['bu'] # Un piège : l'attribut "writeback" est False par defaut
['ga', 'bu', 'meu', 'zo']
[0, 1, 2]

Expressions régulières

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

In [23]:
"abc\n\1def" 
Out[23]:
'abc\n\x01def'
In [24]:
r"abc\n\1def"
Out[24]:
'abc\\n\\1def'
In [25]:
print "abc\n\1def" 
abc
def

In [26]:
print r"abc\n\1def"
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.

In [27]:
import re
m = re.search(r't(.)t\1', 'le tutu') # le \ a une signification spéciale dans les regexp
In [28]:
m
Out[28]:
<_sre.SRE_Match at 0x2503648>

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.

Un test commode

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 :

In [29]:
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.

In [30]:
s = "le cheval de mon cousin ne mange du foin que le dimanche"
p = r'(\b\w*in\b)' # un espace (\b) suivi de 0 ou plusieurs caractères alphabétiques (\w*), des lettres i et n, et d'un espace
re_show(p,s)
le cheval de mon {cousin} ne mange du {foin} que le dimanche 


In [31]:
t = """ceci \\ est un \\backslash
mais cela \\n n'est pas un \n saut de ligne"""
t
Out[31]:
"ceci \\ est un \\backslash\nmais cela \\n n'est pas un \n saut de ligne"
In [32]:
print t
ceci \ est un \backslash
mais cela \n n'est pas un 
 saut de ligne

In [33]:
re_show('\n|\\\\',t)
ceci {\} est un {\}backslash{
}mais cela {\}n n'est pas un {
} saut de ligne 


In [34]:
re_show(r'\n|\\',t) # autre version
ceci {\} est un {\}backslash{
}mais cela {\}n n'est pas un {
} saut de ligne 


Métacaractères

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.

In [35]:
re_show(r'[au\n\\]',t)
ceci {\} est {u}n {\}b{a}cksl{a}sh{
}m{a}is cel{a} {\}n n'est p{a}s {u}n {
} s{a}{u}t de ligne 


Classes prédéfinies

\d  équivalent à [0-9].
\D  équivalent à [^0-9].
\s  équivalent à [ \t\n\r\f\v].
\S  équivalent à [^ \t\n\r\f\v].
\w  équivalent à [a-zA-Z0-9_].
\W  équivalent à [^a-zA-Z0-9_].
Ces séquences peuvent être incluses dans une classe. Par exemple,

[\s\\] contient tous les blancs et le backslash

In [36]:
re_show(r'[\s\\]',t)
ceci{ }{\}{ }est{ }un{ }{\}backslash{
}mais{ }cela{ }{\}n{ }n'est{ }pas{ }un{ }{
}{ }saut{ }de{ }ligne 


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>\tt\{m,n\}$ signifient au moins $m$ fois et au plus $n$ fois ($<regexp>\tt\{n\}$ pour exactement $n$ fois).

In [37]:
re_show(r'o*', 'boogie-woogie')
{}b{oo}g{}i{}e{}-{}w{oo}g{}i{}e{} 


In [38]:
re_show(r'o+', 'boogie-woogie')
b{oo}gie-w{oo}gie 


In [39]:
re_show(r'o{2,4}', 'oh boogie-woogie wooof woooof wooooof')
oh b{oo}gie-w{oo}gie w{ooo}f w{oooo}f w{oooo}of 


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 $+$

In [40]:
s = 'baaababaaababbaababbbabaabaabbbaaaababa'
re_show(r'(a|ba)+',s)
{baaababaaaba}b{baaba}bb{babaabaa}bb{baaaababa} 


Les parenthèses servent aussi à indiquer des groupes (voir plus loin).

\^ et \$ marquent respectivement le début et la fin d'une ligne.

In [41]:
re_show('^m|sh$|^\s',t)
ceci \ est un \backsla{sh}
{m}ais cela \n n'est pas un 
{ }saut de ligne 


Le type RegexObject

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 :

In [42]:
s='baaababaaababbaababbbabaabaabbbaaaababa'
r = re.compile('((a|ba)+)')
r.findall(s)
Out[42]:
[('baaababaaaba', 'ba'),
 ('baaba', 'ba'),
 ('babaabaa', 'a'),
 ('baaaababa', 'ba')]

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

In [43]:
r = re.compile('(?:a|ba)+') # noter le ?: au début de la parenthèse interne
r.findall(s)
Out[43]:
['baaababaaaba', 'baaba', 'babaabaa', 'baaaababa']

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.

In [44]:
print re.split(r'\s|\\',t)
['ceci', '', '', 'est', 'un', '', 'backslash', 'mais', 'cela', '', 'n', "n'est", 'pas', 'un', '', '', 'saut', 'de', 'ligne']

In [45]:
re.sub(r'\s+|\\', ' ',t)
Out[45]:
"ceci   est un  backslash mais cela  n n'est pas un saut de ligne"
In [46]:
re.subn(r'\s+|\\', ' ',t)
Out[46]:
("ceci   est un  backslash mais cela  n n'est pas un saut de ligne", 16)

Le type MatchObject

Les méthodes les plus importantes d'un MatchObject m sont :

  • m.group() ou m.group(0) retourne la chaîne matchée
  • m.start() retourne la position de départ du match
  • m.end() retourne la position de la fin du match
  • m.span() retourne un couple (start, end)
  • m.groups() : une expression peut comporter des groupes, délimités par des parenthèses ...
In [47]:
s='baaababaaababbaababbbabaabaabbbaaaababa'
p = re.compile('((a|ba)+)')
m = p.match(s)
print m.group()
print m.span()
baaababaaaba
(0, 12)

In [48]:
p.sub(lambda x:'.'*len(x.group(0)),s)
Out[48]:
'............b.....bb........bb.........'
In [49]:
p = re.compile('(a(b)c)d(e)') # groupes emboîtés
m = p.match('abcde')
m.groups()
('abc', 'b', 'e')
Out[49]:
('abc', 'b', 'e')
In [50]:
print m.group(0)
print m.group(1)
print m.group(2)
print m.group(3)
abcde
abc
b
e

Groupes non capturants

É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 :

In [51]:
m = re.match('([ij]|[abc])+','aabbcxabb')
print m.groups()
print m.group(0)
print m.group(1)
('c',)
aabbc
c

In [52]:
m = re.match('(?:[ij]|[abc])+','aabbcxabb')

print m.group(0)
m.groups()
aabbc

Out[52]:
()

Les groupes peuvent être nommés en utilisant la syntaxe $\tt(?P<name>...)$

In [53]:
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()
Out[53]:
('cousin', 'dimanche')
In [54]:
p = re.compile(r'(?P<qui>\b\w*in\b).*(?P<quand>\bd\w*e\b)')
m = p.search(s)
m.group('quand')
Out[54]:
'dimanche'

Application d'une fonction aux groupes}

Syntaxe : r.sub(f, s, count=0)

f accepte comme argument un MatchObject et retourne la chaîne à utiliser en remplacement du match.

In [55]:
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 ' '.join([w.capitalize() for w in m.group(1).split()])

p=re.compile(r'<h1>(.*?)</h1>', re.I|re.M)

print re.findall(p,s)
print p.sub(capitalize,s)
['Mon beau cours de python']
<html>
      <body>
      Mon Beau Cours De Python
      <H2>Blabla</H2> et ri et
ra  patati et patata
</body>
</html>

Glouton, ou pas ?

Les opérateurs non-gloutons *?, +?, ??, ou {m,n}?, matchent aussi peu de texte que possible

In [56]:
s = '<html><head><title>Title</title>'
print re.match('<.*>', s).group()
<html><head><title>Title</title>

In [57]:
print re.match('<.*?>', s).group()
<html>

Options de compilation

  • I ou IGNORECASE : insensible à la casse
  • L ou LOCALE : rend \w, \W, \b, \B dépendants de ce que la locale courante considère comme alphanumérique
  • M ou MULTILINE : ^, $ matchent le début et la fin de chaque ligne, en plus du début et de la fin de la chaîne
  • S ou DOTALL : le point . matche un saut de ligne
  • U ou UNICODE : rend \w, \W, \b, \B dépendants de ce qu'Unicode considère comme alphanumérique
  • X ou VERBOSE : les espaces blancs sont ignorés et # est interprété comme le début d'un commentaire

Mode verbeux

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 :

In [58]:
pat_url = re.compile(  r'''
               (?x)( # 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''')
In [58]: