object
class Toto: pass # Attributs comme les new syle du Python 2
a = Toto() # Création d'une instance
print(dir(a))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
class Titi(object): pass #
b = Titi()
print(dir(b))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
print(dir(object))
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
On voit qu'il y a déjà pas mal d'attributs prédéfinis.
Les attributs commençant et finissant par deux soulignés sont les méthodes
spéciales (ou attributs spéciaux). Ils ne sont pas destinés à être appelés
tels quels (on écrira len(x) plutôt que x__len__()
).
Ils permettent la surcharge des opérateurs.
class Toto:
def __len__(self): return 42
a = Toto()
print (len(a))
42
Les méthodes sont définies comme des fonctions ordinaires. Leur premier argument doit toujour être l'instance appelante, conventionnellement appelée self
(équivalent de this dans d'autres langages, mais self
n'est pas un mot réservé).
La méthode spéciale __init__
sert à initialiser les instances.
Elle est appelée imédiatement
après la création d'une instance. Son premier argument est l'instance
elle même, les suivants
sont les paramètres éventuels :
class Client(object):
def __init__(self,nom,prenom):
self.nom = nom
self.prenom = prenom
c = Client('Garcin','Lazare')
print (c.nom, c.prenom)
Garcin Lazare
Les attributs (modifiables) d'un objet sont enregistrés dans un dictionnaire object.__dict__
c.__dict__
{'nom': 'Garcin', 'prenom': 'Lazare'}
On peut ajouter des attributs à la volée :
c.facture = 42
c.__dict__
{'nom': 'Garcin', 'prenom': 'Lazare', 'facture': 42}
À la place de object, on peut passer en argument n'importe quel type existant. La classe crée héritera de ses attributs,
et on pourra les modifier si nécessaire. S'il y a des paramètres à initialiser, on doit appeler la méthode
__init__
de la classe parent.
class Vector(tuple):
def __add__(self,y):
return Vector([a+b for (a,b) in zip(self,y)])
# noter au passage la fonction zip (qui retourne un itérateur)
list(zip('abc','123'))
[('a', '1'), ('b', '2'), ('c', '3')]
x = Vector((1,2,3))
y = Vector((4,5,6))
x+y
(5, 7, 9)
(1,2,3)+(4,5,6)
(1, 2, 3, 4, 5, 6)
print(x,y)
(1, 2, 3) (4, 5, 6)
# Tableau à m lignes et n colonnes à partir d'une liste de mn éléments
class Table(dict):
def __init__(self,m,n,*data):
self.rows = m
self.cols = n
if data:
assert len(data) == m*n
for i in range(m):
for j in range(n): self[i,j] = data[n*i+j]
else:
for i in range(m):
for j in range(n): self[i,j] = 0
def __str__(self):
return '\n'.join([str([self[i,j] for j in range(self.cols)]) for i in range(self.rows)])
A = Table(2,3,1,2,3,4,5,6)
print (A)
A
[1, 2, 3] [4, 5, 6]
{(0, 0): 1, (0, 1): 2, (0, 2): 3, (1, 0): 4, (1, 1): 5, (1, 2): 6}
# Un exemple de listes qui peuvent se multiplier
class Blah(list):
def __mul__(self,mm):
return Blah([x*y for x in self for y in mm])
a = Blah([2,3,4]); b = Blah([5,10]);
print (a, b, a+b, a*b)
[2, 3, 4] [5, 10] [2, 3, 4, 5, 10] [10, 20, 15, 30, 20, 40]
On voit que Blah a hérité de l'addition des listes. On lui a seulement rajouté une méthode de multiplication, qui définit l'opération $*$.
Les instances des classes dérivées sont vues comme des instances de leurs parents.
class Cbase: pass
class Cderivee(Cbase): pass
Ibase=Cbase() ; Iderivee=Cderivee()
print (isinstance(Ibase,Cbase))
print (isinstance(Iderivee,Cbase))
print (isinstance(Ibase,Cderivee))
True True False
# Autre exemple
class defaultdict(dict):
""" Renvoie une valeur par défaut
si une clef n'est pas affectée """
def __init__(self, default=None):
dict.__init__(self)
self.default = default
def __getitem__(self, key): # Noter l'usage de try...except plutôt qu'un test
try:
return dict.__getitem__(self, key)
except KeyError:
return self.default
d = defaultdict(0.0)
e={1:4.5, 2:7.8}
d.update(e)
print (d)
print (d[5])
{1: 4.5, 2: 7.8} 0.0
Les classes de Python 3 possèdent une méthode __new__
qui prend en charge la construction de l'instance.
Elle est utile pour sous-classer les types non mutables :
class CapString(str):
def __new__(cls,s):
return str.__new__(cls,s.lower().capitalize())
def __add__(self,x):
return CapString (str.__add__(self,x.lower()))
x=CapString("toTO")
y = CapString("tITi")
z = x+y
print (x,y,z)
print (type(x), type(y), type(z))
Toto Titi Tototiti <class '__main__.CapString'> <class '__main__.CapString'> <class '__main__.CapString'>
L'attribut __class__
d'un objet fait référence à la classe dont il est une instance.
Il permet aux instances d'accéder aux attributs de la classe elle-même.
La classe suivante possède un compteur qui compte le nombre de ses instances.
class counter:
count = 0
def __init__(self):
self.__class__.count += 1
print (counter)
print (counter.count)
<class '__main__.counter'> 0
c = counter()
print (c.count)
print (counter.count)
1 1
d = counter()
print (d.count)
print (c.count)
print (counter.count)
2 2 2
count est un attribut de la classe counter.
__class__
est un attribut prédéfini de toute instance
d'une classe. C'est une référence à la classe dont self.
est une instance.
count est accessible par référence directe à la classe, avant même la création d'une instance. Chaque instanciation incrémente count, ce qui affecte la classe elle-même.
Sans l'attribut __class__
on aurait
class compteur:
compte = 0
def __init__(self):
self.compte +=1
a=compteur()
print (a.compte)
b=compteur()
print (b.compte)
print (compteur.compte)
1 1 0
Python distingue les attributs publics et privés. Sont privés tous ceux qui commencent (mais ne finissent pas) par un double souligné.
class SomeClass(object):
def PublicMethod(self):
self.__private_field = "encapsulated"
return self.__PrivateMethod()
def __PrivateMethod(self):
return self.__private_field
c = SomeClass()
print (c.PublicMethod())
encapsulated
print (c.__PrivateMethod())
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /tmp/ipykernel_6061/2610493873.py in <module> ----> 1 print (c.__PrivateMethod()) AttributeError: 'SomeClass' object has no attribute '__PrivateMethod'
print (c.__private_field)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /tmp/ipykernel_6061/1571452879.py in <module> ----> 1 print (c.__private_field) AttributeError: 'SomeClass' object has no attribute '__private_field'
# En fait, on peut, mais il ne faut pas ...
c._SomeClass__PrivateMethod()
'encapsulated'
Python n'a pas d'attributs protégés, c'est à dire accessibles seulement dans les classes dérivées. L'usage est de les préfixer par un simple souligné, de manière à ce que les utilisateurs d'une classe comprennent qu'il s'agit d'un détail d'implémentation, mais qu'ils restent accessibles aux classes dérivées.
class BaseClass(object):
def _ProtectedMethod(self):
self._protected = "protected"
class DerivedClass(BaseClass):
def PublicMethod(self):
self._ProtectedMethod()
print (self._protected)
def __PrivateMethod(self):
return self.__private_field
d = DerivedClass()
d.PublicMethod()
protected
Python supporte les propriétés, c'est à dire, les couples de méthodes fget/fset qui sont appelées de manière transparente lorsqu'on accède à un attribut.
class SomeClass(object):
def __init__(self, initial_value):
self.__read_write_prop = initial_value
self.__read_only_prop = initial_value
def __GetReadWriteProp(self):
print ("Someone's reading ReadWriteProp")
return self.__read_write_prop
def __SetReadWriteProp(self, new_value):
print ("Someone's writing ReadWriteProp")
self.__read_write_prop = new_value
ReadWriteProp = property(fget=__GetReadWriteProp,
fset=__SetReadWriteProp)
def __GetReadOnlyProp(self):
print ("Someone's reading ReadOnlyProp")
return self.__read_only_prop
ReadOnlyProp = property(fget=__GetReadOnlyProp)
c = SomeClass("initial")
val = c.ReadWriteProp
print ("val = ",val)
Someone's reading ReadWriteProp val = initial
c.ReadWriteProp = "new"
Someone's writing ReadWriteProp
val = c.ReadWriteProp
Someone's reading ReadWriteProp
print (val)
new
val = c.ReadOnlyProp
Someone's reading ReadOnlyProp
val
'initial'
c.ReadOnlyProp = "new"
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /tmp/ipykernel_6061/1027842784.py in <module> ----> 1 c.ReadOnlyProp = "new" AttributeError: can't set attribute
N'ont pas besoin de connaître l'instance, et ne peuvent pas modifier l'état de leur classe.
class InstanceCounter(object):
num_instances = 0
def __init__(self):
InstanceCounter.num_instances += 1
def GetNumInstances(): # pas de paramètre self
return InstanceCounter.num_instances
GetNumInstances = staticmethod(GetNumInstances) # déclare une méthode statique
a = InstanceCounter()
b = InstanceCounter()
print (InstanceCounter.GetNumInstances()) # c'est 2
InstanceCounter.num_instances = 100
c = InstanceCounter()
print (InstanceCounter.GetNumInstances()) # maintenant, c'est 101
print (a.num_instances)
a.num_instances = 5 # modifie l'attribut de l'instance
print (a.num_instances)
print (InstanceCounter.GetNumInstances()) # mais pas l'attribut statique de la classe
2 101 101 5 101
Elle n'ont besoin que de connaître les paramètres de leur classe,
qui leur est donnée par l'argument cls
(au lieu de self
),
pas ceux des instances. Le mécanisme est similaire. Elles peuvent modifier les paramètres de leur classe.
class SomeClass(object):
def ClassMethod(cls):
print (cls.__name__ ) # c'est le nom de la classe ...
ClassMethod = classmethod(ClassMethod) # déclaration d'une méthode de classe
class DerivedClass(SomeClass):
pass
SomeClass.ClassMethod() # répond "SomeClass"
c = SomeClass()
c.ClassMethod() # encore "SomeClass"
DerivedClass.ClassMethod() # répond "DerivedClass"
d = DerivedClass()
d.ClassMethod() # encore "DerivedClass"
SomeClass SomeClass DerivedClass DerivedClass
Mots clés : try - except - raise - finally
def f():
try:
print ("On essaie ...")
return 1/0
except:
print ("C'est raté ...")
return 2+[2]
finally:
return 42
f()
On essaie ... C'est raté ...
42
La clause finally permet de terminer proprement (en refermant fichiers, sockets, etc. par exemple). Elle sera exécutée quoi qu'il arrive (et avant les éventuels gestionnaires d'exception).
On peut capturer les exceptions :
try:
print (1/0)
except Exception as e: # différent de Python 2 ici
print (e)
division by zero
try:
s = open('toto.txt').read()
except Exception as e:
print (e)
[Errno 2] No such file or directory: 'toto.txt'
Les exceptions prédéfinies sont décrites dans la documentation du module exceptions.
try:
raise EnvironmentError(666, 'External program crashed', 'hello.o')
except EnvironmentError as e:
print (e)
print (e.args)
print (e.errno, e.strerror, e.filename)
[Errno 666] External program crashed: 'hello.o' (666, 'External program crashed') 666 External program crashed hello.o
On peut définir de nouvelles exceptions :
class StupidError(Exception):
def __init__(self):
self.errno = 666
def __str__(self):
return "Stupid Error"
try:
raise StupidError
except Exception as e:
print (e)
print (e.errno)
Stupid Error 666
Le commentaires ordinaires commencent par des #. Une chaîne flottant à l'intérieur d'un programme est vue comme un commentraire. Juste après une définition, elle esyt vue comme une docstring.
# commentaires avec des dièses
"ou bien avec des chaînes littérales"
""" ou encore, sur plusieurs lignes,
avec des triple-quotes """
def racine_carree(x):
"""Cette fonction calcule la racine carrée
du nombre fourni comme paramètre."""
return x**0.5
help(racine_carree)
Help on function racine_carree in module __main__: racine_carree(x) Cette fonction calcule la racine carrée du nombre fourni comme paramètre.
print (racine_carree.__doc__)
Cette fonction calcule la racine carrée du nombre fourni comme paramètre.
Syntaxe :
for i in iterable_object: do_something
Les listes, tuples, chaînes et dictionnaires sont itérables.
for x in range(10): print (x*x),
print()
h = {"un":1, "deux":2, "trois":3}
for key in h: print (key, end=' ')
print()
s = 'gabuzomeu'
for c in s: print (ord(c),end=' ')
0 1 4 9 16 25 36 49 64 81 un deux trois 103 97 98 117 122 111 109 101 117
Les objets itérables possèdent une méthode __iter__
qui renvoie un itérateur.
On l'appelle au moyen de la fonction iter
Un itérateur possède une méthode next() qui renvoie l'élément suivant.
ll = [1, 2, 3, 4, 5]
it = ll.__iter__()
print (next(it), next(it)) # différent de Python 2
1 2
Normalement, on écrit plutôt
ll = [1, 2, 3, 4, 5]
it = iter(ll)
print (next(it))
print (next(it))
for x in it: print (x,end =' ')
1 2 3 4 5
On remarque que l'itérateur se consume au fur et à mesure que next est appelée. Si on continue :
next(it)
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) /tmp/ipykernel_6061/600241529.py in <module> ----> 1 next(it) StopIteration:
for x in obj:
# faire quelque chose
est équivalent à
_iter = iter(obj)
while 1:
try:
x = next(_iter)
except StopIteration:
break
# faire quelque chose
Les générateurs permettent de créer des itérateurs sur des objets qui n'ont pas besoin d'être contruits à l'avance. La syntaxe est identique à celle des fonctions ordinaires, avec le mot clé yield au lieu de return. La fonction retourne alors un générateur.
def fib(): # suite de Fibonacci
a, b = 0, 1
while 1:
yield b; a, b = b, a+b
F=fib()
print ([next(F) for i in range(10)])
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
print ([next(F) for i in range(10)])# On continue
[89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
L'exécution est stoppée après yield et reprend à l'appel suivant de next().
Lorsqu'on remplace les crochets par des parenthèses autour d'une liste en compréhension, on obtient un générateur :
g = (x*x for x in range(10))
for i in range(11): print (next(g), end=' ')
0 1 4 9 16 25 36 49 64 81
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) /tmp/ipykernel_6061/4213998364.py in <module> ----> 1 for i in range(11): print (next(g), end=' ') StopIteration:
On peut définir des classes qui supportent l'itération : il suffit d'implémenter les méthodes $\tt\_\_iter\_\_$ et next:
class countdown(object):
def __init__(self,n):
self.count = n
def __iter__(self):
return self
def __next__(self): # différent
if self.count <= 0:
raise StopIteration
r = self.count
self.count -= 1
return r
c=countdown(10)
print (next(c))
print (list(c)) # la conversion en liste consume l'itérateur
10 [9, 8, 7, 6, 5, 4, 3, 2, 1]
La fonction open permet de créer ou de modifier des objets de type fichier (file objects). Un fichier peut être ouvert dans les modes 'r','w','a','rw' (read,write,append,read-write) et 'b' (binary, pour windows). En python 3, c'est un peu différent, il faut préciser un encodage.
Ces file objects ont des méthodes read et write.
Il ne faut pas oublier de refermer un fichier ouvert avec la méthode close().
f = open('bla.txt','w')
f.write('Ga\nBu\nZo\nMeu\n')
f.close()
!cat bla.txt
Ga Bu Zo Meu
s = open('bla.txt','r').read() # lit tout le fichier sous forme d'une chaîne
s
'Ga\nBu\nZo\nMeu\n'
ll = open('bla.txt').readlines() # retourne une liste de lignes
ll
['Ga\n', 'Bu\n', 'Zo\n', 'Meu\n']
for line in open('bla.txt'): print (line, end=' ') # on peut itérer sur un fichier ouvert
Ga Bu Zo Meu
with open('bla.txt','a') as f: # On utilise de préférence cette syntaxe pour éviter d'oublier de refermer le fichier
f.write('toto') # open() retourne un "context manager", la variable f n'existe que dans le bloc indenté
!cat bla.txt
Ga Bu Zo Meu toto
!cat blu.txt
Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents.
open('blu.txt','r', encoding='utf8').read()
'Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents.\n'
open('blu.txt','rb').read()
b'Il est plus facile de se laver les dents dans un verre \xc3\xa0 pied que de se laver les pieds dans un verre \xc3\xa0 dents.\n'