Fonctionnalités avancées de Python

publicité
Fonctionnalités avancées de Python
Version 1.0.0
David Froger
03 April 2014
Table des matières
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
2
2
3
3
4
4
decorator
2.1 Comparaison avec une composition de fonction
2.2 Syntaxe des décorateurs . . . . . . . . . . . . .
2.3 Passage d’arguments . . . . . . . . . . . . . .
2.4 Exemple réel . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
6
7
7
property
3.1 Attributs, mutateurs et accesseurs .
3.2 property . . . . . . . . . . . .
3.3 descriptor . . . . . . . . . .
3.4 descriptor personnalisé . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
8
10
12
4
metaclass
4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 meta classes personnalisées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Exemples réels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
13
14
15
5
yield
5.1 range VS xrange . .
5.2 Iterator . . . . . . . .
5.3 Generator . . . . . .
5.4 Generator expression
.
.
.
.
16
16
16
17
17
with
6.1 Exemples d’utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Implémentation d’un manageur de contexte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3 Utilisation du module contextlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
18
19
20
2
3
6
Introduction
1.1 Fonction pour itérer sur des séquences . . .
1.2 map, reduce . . . . . . . . . . . . . . . . .
1.3 Compréhension de listes et de dictionnaires
1.4 Tout est objet . . . . . . . . . . . . . . . .
1.5 Fermeture (closure) . . . . . . . . . . .
1.6 finally . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
Remerciements
20
Cette présentation traite des fonctionnalités avancées du langage Python. Souvent méconnues, elles permettent au
développeur d’écrire un code plus flexible, sûr et concis.
Cette courte présentation ne rentrera pas dans tous les détails à connaître pour utiliser pratiquement ces fonctionnalités,
mais signalera plutôt leur existence et ce qu’elles peuvent apporter, en donnant les concepts clefs pour aborder plus
facilement les documentations, livres ou articles plus complets traitant de ces sujets.
Les coroutines et yield from sont passés sous silence pour le moment, et pourront faire l’objet de deux nouvelles
sections dans le futur.
1 Introduction
Nous commençons cette présentation de Python avancé par quelques exemples disparates de fonctionnalités de Python
généralement connues, se situant à la limite entre le Python de base et le Python avancé.
1.1 Fonction pour itérer sur des séquences
La fonction enumerate permet d’itérer sur les éléments d’une séquence avec leurs indices :
words = [’Rien’, ’ne’, ’sert’, ’de’, ’courir’]
for iword, word in enumerate(words):
print iword, word
0
1
2
3
4
Rien
ne
sert
de
courir
La fonction zip permet de parcourir deux séquences en même temps :
words = [’Rien’, ’ne’, ’sert’, ’de’, ’courir’]
lengths = [4, 2, 4, 2, 6]
for iword,(word,length) in enumerate( zip(words,lengths) ):
print iword, word, length
0
1
2
3
4
Rien 4
ne 2
sert 4
de 2
courir 6
1.2 map, reduce
Python intègre les fonctions map et reduce :
words = [’Rien’, ’ne’, ’sert’, ’de’, ’courir’]
length = map(len, words)
sum = reduce(lambda a,b:a+b, length)
print length, sum
[4, 2, 4, 2, 6] 18
1.3 Compréhension de listes et de dictionnaires
La compréhension de liste permet de créer une liste à partir d’une seule expression :
print [ i**2+1 for i in range(5) ]
[1, 2, 5, 10, 17]
Il est possible d’inclure une condition :
print [ i**2+1 for i in range(5) if i%2 == 0]
[1, 5, 17]
On peut créer des dictionnaires de la même façon :
Introduit dans la version 2.7.
d = {i: i**2 + 1 for i in range(5) if i%2 == 0}
for key,value in d.iteritems():
print "%i => %2i" % (key, value)
0 => 1
2 => 5
4 => 17
1.4 Tout est objet
Les fonctions en Python sont des objets. On peut par exemple stocker une référence sur une fonction, et passer cette
référence en argument à une autre fonction.
def carre(x):
return x**2
def compute(func,seq):
return [ func(x) for x in seq ]
func = carre
seq = range(5)
print compute(func,seq)
[0, 1, 4, 9, 16]
De même, les classes sont des objets :
class Foo:
def __init__(self,a,b):
self.a = a
self.b = b
def operate(self):
return self.a + self.b
def compute(cls, a, b):
instance = cls(a,b)
return instance.operate()
cls = Foo
print compute(cls,10,20)
30
1.5 Fermeture (closure)
Les règles de visibilité des variables permettent de créer des fermetures. Ici, la fonction add_const utilise la valeur
de const définie dans l’espace de nom de la fonction qui l’englobe.
def create_add_const(const):
def add_const(number):
return number + const
return add_const
add_const = create_add_const(10)
print add_const(2)
12
1.6 finally
Le mot clef finally permet d’exécuter du code quelque soit le résultat d’un code dans un block try :
— si aucune exception n’a été levée,
— si une exception a été levée et capturée,
— si une exception a été levée sans être capturée.
Cela permet de fermer dans le block finally une ressource ouverte (fichier, socket, ...).
Voici un exemple d’utilisation de finally 1 :
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print "division by zero!"
else:
print "result is", result
finally:
print "executing finally clause"
1. Exemple tiré de la documentation officielle (https ://docs.python.org/2/tutorial/errors.html#defining-clean-up-actions)
divide(2, 1)
print
divide(2, 0)
print
try:
divide("2", "1")
except TypeError:
print "TypeError catched"
result is 2
executing finally clause
division by zero!
executing finally clause
executing finally clause
TypeError catched
2 decorator
Les décorateurs permettent d’annoter un code source Python pour modifier le comportement d’une fonction ou d’une
classe.
2.1 Comparaison avec une composition de fonction
Contrairement à la composition de fonction , dont un exemple simple est, par exemple :
𝑦 = 𝑔(𝑓 (𝑥))
et dont l’équivalent en Python serait, par exemple,
def f(x):
return x+20
def g(x):
return x+300
print g( f(1) )
321
l’action d’un décorateur se symbolise plutôt par
𝑦 = (𝑑(𝑓 ))(𝑥)
Par exemple, la fonction d est un décorateur qui prend en argument un fonction f, et retourne une fonction nouvelle_f,
qui vérifie que le nombre en argument est positif, puis appelle f :
def d(f):
def nouvelle_f(x):
if x < 0:
raise ValueError, "On n’accepte que les nombres positifs."
return f(x)
return nouvelle_f
def f(x):
return x+100
print d(f) (-1)
Traceback (most recent call last):
...
ValueError: On n’accepte que les nombres positifs.
2.2 Syntaxe des décorateurs
L’exemple précédent peut être réécrit :
def d(f):
def nouvelle_f(x):
if x < 0:
raise ValueError, "On n’accepte que les nombres positifs."
return f(x)
return nouvelle_f
def f(x):
return x+100
f = d(f)
print f(-1)
Traceback (most recent call last):
...
ValueError: On n’accepte que les nombres positifs.
Python fournit une syntaxe pour décorer une fonction de cette manière, en annotant la ligne précédant la définition de
la fonction du nom du décorateur, précédé du symbole @ :
def d(f):
def nouvelle_f(x):
if x < 0:
raise ValueError, "On n’accepte que les nombres positifs."
return f(x)
return nouvelle_f
@d
def f(x):
return x+100
print f(-1)
Traceback (most recent call last):
...
ValueError: On n’accepte que les nombres positifs.
2.3 Passage d’arguments
Il est possible d’utiliser les arguments (et arguments par mot clef) de la fonction décorée. Il est également possible de
définir des décorateurs avec des arguments.
2.4 Exemple réel
Voici un exemple d’application Web basé sur Flask, qui associe la fonction login à l’URL /login, pour des
méthodes GET et POST 2 :
@app.route(’/login’, methods=[’GET’, ’POST’])
def login():
if request.method == ’POST’:
do_the_login()
else:
show_the_login_form()
D’autre exemples réels seront rencontrés dans la suite de la présentation.
3 property
Les properties permettent d’implémenter les fonctionnalités des mutateurs (setters) et accesseurs (getters)
tout en ayant l’impression de manipuler directement un attribut.
3.1 Attributs, mutateurs et accesseurs
Supposons que l’on crée une classe Rectangle de la sorte :
class Rectangle(object):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
r = Rectangle(10,2)
print r.width, r.height, r.area()
r.width *= 10
print r.width, r.height, r.area()
10 2 20
100 2 200
Bien que cette classe fonctionne, elle ne fait pas de vérification sur ses données d’entrée, et permet par exemple de
créer des longueurs négatives :
r = Rectangle(10,5)
r.width *= -1
print r.width, r.height, r.area()
2. Exemple tiré de la documentation de Flask. (http ://flask.pocoo.org/docs/quickstart/#http-methods)
-10 5 -50
Pour introduire un contrôle sur les données d’entrée, on peut choisir d’utiliser des mutateurs et des accesseurs :
class Rectangle(object):
def __init__(self, width, height):
self._width = width
self._height = height
def area(self):
return self._width * self._height
def set_width(self,width):
if width < 0:
raise ValueError, "width must be positive"
self._width = width
def set_height(self,height):
if height < 0:
raise ValueError, "height must be positive"
self._height = height
def get_width(self):
return self._width
def get_height(self):
return self._height
r = Rectangle(10,2)
print r.get_width(), r.get_height(), r.area()
r.set_width( r.get_width() * 10 )
print r.get_width(), r.get_height(), r.area()
try:
r.set_width( r.get_width() * -1)
except ValueError:
print "ValueError catched"
10 2 20
100 2 200
ValueError catched
Cela fonctionne mais pose un premier problème : tout code “client” utilisant la classe Rectangle est maintenant “cassé” et doit être mis à jour. Par exemple, chaque occurrence de r.width doit être remplacée par
r.get_width(). Dans le cas du développement d’une bibliothèque, cela déplaira aux utilisateurs.
On peut vouloir se fixer comme règle de systématiquement utiliser des mutateurs/accesseurs pour chaque attribut et
se garder ainsi la possibilité de modifier les lectures et écritures des attributs. Mais cela nuit considérablement à la
lisibilité du code, par exemple : r.width *= 2 devient r.set_width( r.get_width() * 2).
Les properties permettent de remédier à ce problème.
3.2 property
La classe Rectangle peut se réécrire ainsi :
class Rectangle(object):
def __init__(self, width, height):
self._width = width
self._height = height
def area(self):
return self._width * self._height
def set_width(self,width):
if width < 0:
raise ValueError, "width must be positive"
self._width = width
def set_height(self,height):
if height < 0:
raise ValueError, "height must be positive"
self._height = height
def get_width(self):
return self._width
def get_height(self):
return self._height
width = property(get_width,set_width)
height = property(get_height,set_height)
r = Rectangle(10,2)
print r.width, r.height, r.area()
r.width *= 10
print r.width, r.height, r.area()
try:
r.width *= -1
except ValueError:
print "ValueError catched"
10 2 20
100 2 200
ValueError catched
Lorsqu’on lit la valeur de l’attribut width avec r.width, la méthode get_witdh est appelée, et lorsque l’on écrit
dans l’attribut r.width, la méthode set_width est appelée. Cela offre à notre classe une interface commode.
On peut aussi utiliser le décorateur @property :
class Rectangle(object):
def __init__(self, width, height):
self._width = width
self._height = height
def area(self):
return self._width * self._height
@property
def width(self):
return self._width
@width.setter
def width(self,width):
if width < 0:
raise ValueError, "width must be positive"
self._width = width
@property
def height(self):
return self._height
@height.setter
def height(self,height):
if height < 0:
raise ValueError, "height must be positive"
self._height = height
r = Rectangle(10,2)
print r.width, r.height, r.area()
r.width *= 10
print r.width, r.height, r.area()
try:
r.width *= -1
except ValueError:
print "ValueError catched"
10 2 20
100 2 200
ValueError catched
3.3 descriptor
Plusieurs éléments de Python, comme les fonctions, les méthodes de classe, et les property, sont implémentés à l’aide
de descriptor.
Un descripteur est une classe qui implémente les méthodes spéciales __get__, et possiblement __set__ (et aussi
possiblement __del__).
Lorsque l’instance d d’un descripteur est l’attribut d’une classe cls d’instance c, accéder à c.d appelle la méthode
__get__ du descripteur.
On peut par exemple réécrire le décorateur intégré (built-in) de Python @property de cette façon 3 :
class my_property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
3. Exemple tiré d’un article de la documentation officielle de Raymond Hettinger (https ://docs.python.org/2/howto/descriptor.html#properties).
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can’t set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can’t delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
class Rectangle(object):
def __init__(self, width, height):
self._width = width
self._height = height
def area(self):
return self._width * self._height
@my_property
def width(self):
return self._width
@width.setter
def width(self,width):
if width < 0:
raise ValueError, "width must be positive"
self._width = width
@my_property
def height(self):
return self._height
@height.setter
def height(self,height):
if height < 0:
raise ValueError, "height must be positive"
self._height = height
r = Rectangle(10,2)
print r.width, r.height, r.area()
r.width *= 10
print r.width, r.height, r.area()
try:
r.width *= -1
except ValueError:
print "ValueError catched"
10 2 20
100 2 200
ValueError catched
3.4 descriptor personnalisé
Cela ouvre de nombreuses portes : plutôt que d’utiliser @property, on peut créer son propre descriptor. Par
exemple, dans le cas de la classe Rectangle, un descripteur réutilisable qui n’accepte que des valeurs positives 4 :
from weakref import WeakKeyDictionary
class NonNegative(object):
"""A descriptor that forbids negative values"""
def __init__(self, default):
self.default = default
self.data = WeakKeyDictionary()
def __get__(self, instance, owner):
# we get here when someone calls x.d, and d is a NonNegative instance
# instance = x
# owner = type(x)
return self.data.get(instance, self.default)
def __set__(self, instance, value):
# we get here when someone calls x.d = val, and d is a NonNegative instance
# instance = x
# value = val
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self.data[instance] = value
class Rectangle(object):
width = NonNegative(0)
height = NonNegative(0)
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
r = Rectangle(10,2)
print r.width, r.height, r.area()
r.width *= 10
print r.width, r.height, r.area()
try:
r.width *= -1
4.
Exemple
tiré
de
l’article
Python
Descriptors
Demystified
wer.ipython.org/urls/gist.github.com/ChrisBeaumont/5758381/raw/descriptor_writeup.ipynb).
de
Chris
Beaumont
(http
://nbvie-
except ValueError:
print "ValueError catched"
10 2 20
100 2 200
ValueError catched
4 metaclass
Les méta-classes permettent de modifier la création d’un objet class (on parle bien de la création de la classe, et non
de son instantiation).
4.1 Introduction
Lorsque Python fait l’analyse grammaticale (parsing) de :
from math import sqrt
class GoldRectangle(object):
gold_number = 0.5 * (1 + sqrt(5))
def __init__(self,width):
self.width = width
self.height = width / GoldRectangle.gold_number
def area(self):
return self.width * self.height
print GoldRectangle
r = GoldRectangle(10)
print r.width, r.height, r.area()
<class ’GoldRectangle’>
10 6.1803398875 61.803398875
pour créer l’objet GoldRectangle, il collecte trois informations :
— le nom de la classe GoldRectangle,
— les classes de base, ici on n’a que object,
— les attributs de la classe, gold_number, __init__ et area, auxquels sont rajoutés certains attibuts spéciaux.
Ces trois informations sont ensuite passées à une metaclass, qui est chargée de créer l’objet class (l’objet
GoldRectangle que l’on a affiché).
Cette metaclass est (pour les classes new-style) par défaut l’objet type qui, outre son rôle de donner le type
d’un objet, est aussi utilisé pour créer des classes.
Ainsi, la classe GoldRectangle créée ci-dessus peut être créée alternativement de cette façon :
from math import sqrt
gold_number = 0.5 * (1 + sqrt(5))
def __init__(self,width):
self.width = width
self.height = width / GoldRectangle.gold_number
def area(self):
return self.width * self.height
name = "GoldRectangle"
bases = (object,)
attributes = {’gold_number’: gold_number, ’__init__’: __init__, ’area’:area}
GoldRectangle = type(name, bases, attributes)
print GoldRectangle
r = GoldRectangle(10)
print r.width, r.height, r.area()
<class ’GoldRectangle’>
10 6.1803398875 61.803398875
L’utilisation des meta-classes consiste à remplacer la metaclass type par sa metaclass personnalisée.
4.2 meta classes personnalisées
Par exemple, la méta-classe AccessorType ajoute à la classe qu’elle crée les property correspondants aux méthodes de classe commençant par les préfixes get_, set_ et del_ 5 :
class AccessorType(type):
def __init__(self, name, bases, d):
type.__init__(self, name, bases, d)
accessors = {}
prefixs = ["get_", "set_", "del_"]
for k in d.keys():
v = getattr(self, k)
for i in range(3):
if k.startswith(prefixs[i]):
accessors.setdefault(k[4:], [None, None, None])[i] = v
for name, (getter, setter, deler) in accessors.items():
# create default behaviours for the property - if we leave
# the getter as None we won’t be able to getattr, etc..
# [...] some code that implements the above comment
setattr(self, name, property(getter, setter, deler, ""))
class Rectangle(object):
__metaclass__ = AccessorType
def __init__(self,width,height):
self._width = width
self._height = height
def get_width(self):
return self._width
def set_width(self,width):
5. Exemple tiré de l’article de Eli Bendersky. (http ://eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example/)
self._width = width
def get_height(self):
return self._height
def set_height(self,height):
self._height = height
r = Rectangle(10,2)
print r.width
10
4.3 Exemples réels
On rencontre souvent les méta-classes dans des bibliothèques pour créer des modèles de données. Par exemple,
PyTables permet de décrire le schéma d’une table de base de donnée de cette façon 6 :
class Particle(IsDescription):
name
= StringCol(16)
idnumber = Int64Col()
ADCcount = UInt16Col()
TDCcount = UInt8Col()
grid_i
= Int32Col()
grid_j
= Int32Col()
pressure = Float32Col()
energy
= Float64Col()
#
#
#
#
#
#
#
#
16-character String
Signed 64-bit integer
Unsigned short integer
unsigned byte
32-bit integer
32-bit integer
float (single-precision)
double (double-precision)
SQLAlchemy permet de faire 7 :
from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = ’users’
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
password = Column(String)
def __repr__(self):
return "<User(name=’%s’, fullname=’%s’, password=’%s’)>" % (
self.name, self.fullname, self.password)
WTForms permet de créer des formulaires HTML avec 8 :
from wtforms import Form, BooleanField, StringField, validators
class RegistrationForm(Form):
username
= StringField(’Username’, [validators.Length(min=4, max=25)])
email
= StringField(’Email Address’, [validators.Length(min=6, max=35)])
accept_rules = BooleanField(’I accept the site rules’, [validators.InputRequired()])
6. Exemple tiré du tutoriel officiel de PyTables. (http ://pytables.github.io/usersguide/tutorials.html#declaring-a-column-descriptor)
7. Exemple tiré du tutoriel officiel de SQLalchemy. (http ://docs.sqlalchemy.org/en/rel_0_9/orm/tutorial.html#create-a-schema)
8. Exemple tiré du tutoriel officiel de WTForms. (http ://wtforms.readthedocs.org/en/latest/crash_course.html#getting-started)
5 yield
Le mot clef yield permet de créer facilement des generator, qui sont des fonctions itérables.
5.1 range VS xrange
La fonction range crée une liste de N éléments, sur laquelle il est possible d’itérer :
for i in range(4):
print i
0
1
2
3
La fonction xrange, en revanche, n’alloue pas de l’espace mémoire pour les N éléments, mais pour un seul élément.
Il est possible d’itérer de la même manière :
for i in xrange(4):
print i
0
1
2
3
5.2 Iterator
Un iterator est une classe qui implémente les méthodes spéciales __iter__ et next, pour devenir itérable.
La fonction xrange peut être implémentée de cette façon :
class my_xrange:
def __init__(self, last):
self.current = 0
self.last = last
def __iter__(self):
return self
def next(self):
if self.current >= self.last:
raise StopIteration
self.current += 1
return self.current - 1
for i in my_xrange(4):
print i
0
1
2
3
La classe my_xrange implémente __iter__ qui doit retourner l’iterateur en question. Dans notre cas simple, il
s’agit de l’instance elle-même, mais pour un conteneur, il retournerait une valeur différente de self).
La fonction next retourne la valeur de l’itération suivante, et lance l’exception StopIteration pour signaler la
fin de la boucle.
5.3 Generator
my_xrange peut être implémentée plus aisément avec un générateur, qui est une simple fonction utilisant le mot clef
yield.
Lorsque l’instruction yield est atteinte, l’execution du programme est transférée du générateur à l’appelant, et est
redonnée au générateur lors de la transition à l’itération suivante de la boucle de l’appelant.
def my_xrange(last):
current = 0
while current < last:
yield current
current += 1
for i in my_xrange(4):
print i
0
1
2
3
5.4 Generator expression
À l’instar des compréhensions de liste :
for x in [ i**2 for i in range(7) if i%2 == 0]:
print x
0
4
16
36
il est possible d’utiliser les generator expression pour ne stocker en mémoire qu’un seul élément :
for x in ( i**2 for i in range(7) if i%2 == 0):
print x
0
4
16
36
6 with
Le mot clef with est utilisé par les manageurs de contexte, qui permettent d’ouvrir et de libérer une ressource (de la
même manière que finally) de façon fiable.
6.1 Exemples d’utilisation
Par exemple, le mot clef with peut s’utiliser pour ouvrir une ressource (ici un fichier), et la fermer automatiquement
en sortant du contexte d’exécution de with, qu’une exception ait été levée ou non.
def foo(f):
pass
def bar(f):
raise ValueError
with open(’file.txt’,’w’) as f:
foo(f)
with open(’file.txt’,’w’) as f:
bar(f)
Dans les deux cas, le fichier file.txt est fermé.
Un verrou utilisé par des threads peut être acquis et libéré avec les méthodes aquire() et release(), mais
également avec un manageur de contexte. Ainsi
from threading import Thread, Lock
import time
class Worker(Thread):
id_ = 0
def __init__(self,lock):
Thread.__init__(self)
self.lock = lock
self.id_ = Worker.id_
Worker.id_ += 1
def run(self):
self.lock.acquire()
print ’Worker %i has acquired lock’ % self.id_
time.sleep(0.1)
self.lock.release()
print ’Worker %i has released lock’ % self.id_
lock = Lock()
w0 = Worker(lock)
w1 = Worker(lock)
w0.start()
time.sleep(0.01)
w1.start()
w0.join()
w1.join()
Worker
Worker
Worker
Worker
0
0
1
1
has
has
has
has
peut se réécrire
acquired
released
acquired
released
lock
lock
lock
lock
from threading import Thread, Lock
import time
class Worker(Thread):
id_ = 0
def __init__(self,lock):
Thread.__init__(self)
self.lock = lock
self.id_ = Worker.id_
Worker.id_ += 1
def run(self):
with self.lock:
print ’Worker %i has acquired lock’ % self.id_
time.sleep(0.1)
print ’Worker %i has releases lock’ % self.id_
lock = Lock()
w0 = Worker(lock)
w1 = Worker(lock)
w0.start()
time.sleep(0.01)
w1.start()
w0.join()
w1.join()
Worker
Worker
Worker
Worker
0
0
1
1
has
has
has
has
acquired
releases
acquired
released
lock
lock
lock
lock
6.2 Implémentation d’un manageur de contexte
Pour être utilisable avec with, une ressource doit implémenter les méthodes spéciales __enter__, qui ouvre la
ressource, et __exit__, qui ferme la ressource (et possiblement gère une exception levée) :
class Ressource:
def __init__(self):
self._opened = False
def open(self):
print "open ressource"
self._opened = True
def close(self):
print "close ressource"
self._opened = False
def use(self):
print "use ressource"
def __enter__(self):
self.open()
return self
def __exit__(self,exc_type,exc_value,traceback):
self.close()
with Ressource() as r:
r.use()
open ressource
use ressource
close ressource
6.3 Utilisation du module contextlib
Le module contextlib permet également d’implémenter un manageur de contexte en décorant un générateur à
l’aide de @contextmanager.
Par exemple :
from contextlib import contextmanager
class Ressource:
def __init__(self):
self._opened = False
def open(self):
print "open ressource"
self._opened = True
def close(self):
print "close ressource"
self._opened = False
def use(self):
print "use ressource"
@contextmanager
def ressource():
r = Ressource()
r.open()
yield r
r.close()
with ressource() as r:
r.use()
open ressource
use ressource
close ressource
7 Remerciements
Cédric Doucet pour la relecture.
Téléchargement