LangageObjetsComplements

[wiki: Orientation objets : compléments] [#license ©]

PageOutline

#!comment
Les types prédéfinis sont des classes ?
Les littéraux des objets ?


QUID DES METACLASSES ?

QUID DES CLASSES ABSTRAITES ? (http://legacy.python.org/dev/peps/pep-3119/)
from collections import Iterable
isinstance('ab', Iterable)
isinstance([1, 2], Iterable)
# comme on l'a vu un objet qui a une methode iter()
# et une next() est considere comme un iterable
class Foo():
def iter(self):
return self
def next(self):
# ceci naturellement est bidon
return

foo = Foo()
isinstance(foo, Iterable)

MRO : il y a des cas insolubles, rejetés par l'nterpréteur :
# en version code
class X(object): pass
class Y(object): pass
class XY(X, Y): pass
class YX(Y, X): pass

# on essaie de créer une sous-classe de XY et YX
try:
class Class(XY,YX): pass
# mais ce n'est pas possible
except Exception as e:
print "On ne peut pas créer Class"
print e


Attributs cachés

Dans un objet, les attributs ordinaires sont stockés dans un attribut spécial nommé dict. Chaque instance possède également un attribut nommé class qui pointe vers sa classe.

Dans une classe, on trouve également des attributs spéciaux :

  • doc : documentation de la classe,
  • dict : attributs de la classe,
  • name : nom de la classe,
  • bases : tuple contenant les classes de bases de la classe courante.
  • mro : ordre de parcours des ancêtres de la classe.
  • module : nom du module où la classe est définie (main en mode interactif).
#!div style="width:600px"


{{{
#!python
class A1 : pass
class A2 : pass

class B(A1,A2):
def init(self,value): self.data = value
def display(self) : print("data:",self.data)

b = B("abc")

print(b.dict)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
{'data': 'abc'}
</pre>

}}}

#!div style="width:600px"


{{{
#!python
print(b.dict['data'])


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
abc
</pre>

}}}

#!div style="width:600px"


{{{
#!python
print((b.class, ))


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
(<class main.B at 0x0000000004206588>,)
</pre>

}}}

#!div style="width:600px"


{{{
#!python
print(b.class.name)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
B
</pre>

}}}

#!div style="width:600px"


{{{
#!python
print(b.class.bases)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
(<class main.A1 at 0x0000000004180EE8>, <class main.A2 at 0x00000000042065E8>)
</pre>

}}}

#!div style="width:600px"


{{{
#!python
print(b.class.dict)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
{'module': 'main', 'display': <function display at 0x000000000422A588>, 'init': <function init at 0x000000000422A5F8>, 'doc': None}
</pre>

}}}

Attributs pseudo-privés

Au sein d’une instruction composée class, tous les noms qui sont préfixés par un double souligné __ (et non à la fin), sont « magiquement » manipulés par l’interpréteur Python qui insère devant le nom un simple souligné _ et le nom de la classe courante. Ainsi, les attributs recoivent un nom qui devient spécifique à la classe, et qui ne risque plus d’être redéfini par erreur dans les classes dérivées. On parle d’attributs « pseudo-privés », non pas parce que l’accès à ces attributs est véritablement interdit, mais parce que l’altération automatique de leurs noms complique leur accès de l’extérieur.

#!div style="width:600px"


{{{
#!python
class Demo:
public_data = "public data"
__private_data = "private data"
def public_method(self): print("public method()")
def __private_method(self): print("private method()")

dir(Demo)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
['_Demo__private_data',
 '_Demo__private_method',
 'doc',
 'module',
 'public_data',
 'public_method']
</pre>

}}}

#!div style="width:600px"


{{{
#!python
print(Demo.public_data)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
public data
</pre>

}}}

#!div style="width:600px"


{{{
#!python
print(Demo.__private_data)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-5a651a2f66b8> in <module>()
----> 1(print(Demo.__private_data)


AttributeError: class Demo has no attribute '__private_data'
</pre>


}}}

#!div style="width:600px"


{{{
#!python
print(Demo._Demo__private_data)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
private data
</pre>

}}}

#!div style="width:600px"


{{{
#!python
d = Demo()
d.public_method()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
public method()
</pre>

}}}

#!div style="width:600px"


{{{
#!python
d.__private_method()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-24-f98a2e3c7104> in <module>()
----> 1 d.__private_method()


AttributeError: Demo instance has no attribute '__private_method'
</pre>


}}}

#!div style="width:600px"


{{{
#!python
d._Demo__private_method()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
private method()
</pre>

}}}

A la différence d’autres langages,toutes les données d’un objet réside dans l’objet lui-même, quelle que soit la classe et la méthode à l’origine de ces données. Cela peut générer des interactions involontaires, en particulier dans le cas d’un héritage multiple venant de classes issues de développeurs indépendants. Les attributs pseudo-privés évitent ces interactions non voulues.)

#!div style="width:600px"


{{{
#!python
class C1:
def set1(self): self.__X = 1
def get1(self): return self.__X

class C2:
def set2(self): self.__X = 2
def get2(self): return self.__X

class C3(C1,C2): pass

obj = C3()
obj.set1()
obj.set2()
print(obj.get1(),obj.get2())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
1 2
</pre>

}}}

Propriétés

On peut définir dans une classe des attributs particuliers de type « propriété » (property). Ces propriétés ressemblent à des variables membres, et s’utilisent comme des variables membres, mais en réalité le fait de lire une propriété déclenche l’utilisation du « getter » qui lui est attaché, et le fait d’affecter une nouvelle valeur à cette propriété déclenche l’utilisation du « setter » attaché (si et seulement si il est défini). Typiquement, « getter » et « setter » manipulent une variable interne pseudo-privée.

Ci-dessous, nous définissons pour la classe Vector des propriétés x et y, dont la lecture déclenche un appel à getx() ou gety(), qui renvoient les valeurs des variables pseudo-privées x ou y des instances de Vector. Seule la propriété x a été associée à un « setter ». y, en l’absence de « setter », est en lecture seule : on ne peut pas lui affecter de valeur (par contre on peut le faire sur __y via init()).

#!div style="width:600px"


{{{
#!python
class Vecteur:
def init(self,u=0,v=0):
self.__x = u
self.__y = v
def getx(self):
return self.__x
def setx(self,u):
self.__x = u
x = property(getx,setx)
def gety(self):
return self.__y
y = property(gety)

v = Vecteur()
v.init(3, -4)
print(v.x,v.y)
v.x = 2
print(v.x)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
3 -4
2
</pre>

}}}

#!div style="width:600px"


{{{
#!python
v.y = 5


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-29-916c9ff1d97a> in <module>()
----> 1 v.y = 5


AttributeError: can't set attribute
</pre>


}}}

Ce qui est remarquable ici, c’est que Python ne force pas une encapsulation précoce et inutile. On peut très bien développer une première version de classe avec des attributs publics, avoir des clients, puis décider ultérieurement d’en faire des propriétés, si le besoin s’en fait sentir.

On a vu aussi, dans notre exemple, qu’il est facile de faire des propriétés « read-only » en ne déclarant que les « getters ». Il est cependant plus classique de fournir à la fois la méthode de lecture et d’écriture, notamment pour controler qu’on n’enregistre que des valeurs valides dans une propriété. Par exemple, si nos vecteurs ne peuvent contenir que des valeurs entre -1 et 1 :

#!div style="width:600px"


{{{
#!python
class Vecteur:
def init(self,u=0,v=0):
self.x = u
self.y = v
def getx(self):
return self.__x
def setx(self,x):
if (x<-1): self.__x = -1
elif (x>1): self.__x = 1
else: self.__x = x
x = property(getx,setx)
def gety(self):
return self.__y
def sety(self,y):
if (y<-1): self.__y = -1
elif (y>1): self.__y = 1
else: self.__y = y
y = property(gety,sety)

v = Vecteur()
v.init(3, -4)
print(v.x,v.y)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
1 -1
</pre>

}}}

Variables de classe

On peut ajouter des données à une classe (et pas seulement des méthodes), auquel cas elles sont en quelque sorte partagées et visibles pour toutes les instances de la classe.

En effet, quand vous demandez à lire la donnée nommée x de l’instance obj, ce qui se note obj.x, l’interpréteur Python cherche d’abord le nom dans l’instance elle-même, puis à défaut dans la classe, puis dans ses ancêtres, comme pour n’importe quel attribut.

Dans l’exemple ci-dessous, on dote la classe Vecteur de variables x et y qui contiennent des valeurs par défaut pour les instances. On peut créer ces variables directement lors de la création de classe, comme nous le faisons pour x, ou l’ajouter ultérieurement, comme nous le faisons pour y.

#!div style="width:600px"


{{{
#!python
class Vecteur:
x = 0

Vecteur.y = 0

v = Vecteur()

print(Vecteur.x,Vecteur.y)
print(v.x,v.y)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
0 0
0 0
</pre>

}}}

Par contre, si vous affectez une nouvelle valeur à obj.x, et que ce nom n’existe pas encore dans l’instance, un nouvel attribut est créé à cette occasion, dans l’instance. Formulons le à nouveau : en cas de lecture, l’interpréteur cherche l’attribut dans l’instance concernée, puis dans sa classe et ses ancêtres, mais en cas d’affectation d’une nouvelle valeur, l’attribut est ajouté à l’instance si il n’existe pas déjà.

#!div style="width:600px"


{{{
#!python
v.x, v.y = 10, 20

print(Vecteur.x,Vecteur.y)
print(v.x,v.y)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
0 0
10 20
</pre>

}}}

Un langage très dynamique

Les instructions class et les instructions def, sont traitées à l’exécution comme n’importe quelles instructions. Elles retournent des objets, certes un peu spéciaux, et leur assigne un nom dans l’espace de nom courant. Avec ces objets, on peut réaliser des manipulations impossibles dans d’autres langages, comme par exemple créer une méthode d’abord en tant que fonction, à l’extérieur de la classe, puis la rattacher à posteriori.

#!div style="width:600px"


{{{
#!python
class A:
def init(self,value): self.data = value
def display(self) : print("data:",self.data)

a = A('bonjour')

def my_display_upper(self):
print("data:",self.data.upper())

A.display_upper = my_display_upper

a.display_upper()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
data: BONJOUR
</pre>

}}}

Une méthode peut s’invoquer à travers le nom de classe, mais dans ce cas il ne faut pas oublier de redonner le nom de l’objet à traiter comme premier argument (self) :

#!div style="width:600px"


{{{
#!python
A.display_upper(a)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
data: BONJOUR
</pre>

}}}

Je peux toujours aussi appeler directement la fonction à travers son nom original (my_display_upper) :

#!div style="width:600px"


{{{
#!python
my_display_upper(a)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
data: BONJOUR
</pre>

}}}

On peut même s’amuser, comme ci-dessous, à décrocher la fonction de la classe (en effacant le nom), et à l’accrocher directement à l’instance. Mais dans ce cas, la définition automatique de self n’est pas réalisée (ce mécanisme n’est actif que pour les fonctions attachées dans des classes). On doit alors redonner a comme argument à l’appel de fonction.

#!div style="width:600px"


{{{
#!python
del A.display_upper

a.display_upper = my_display_upper

a.display_upper(a)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
data: BONJOUR
</pre>

}}}

Les encoches

La définition dans une classe d’un attribut spécial nommé slots permet de limiter la liste des attributs autorisés (sans pour autant les créer). Cela permet d’éviter des erreurs de frappe involontaire, de faire certaines optimisations, mais en contre-partie cela peut affecter l’existence de l’attribut spécial dict, et perturber le fonctionnement d’outils génériques qui s’appuient sur ce dictionnaire interne.

#!div style="width:600px"


{{{
#!python
class DemoSlots:
slots = ['att1','att2']

ds = DemoSlots()
print(ds.att1)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-26-c8617deaf333> in <module>()
      3 
      4 ds = DemoSlots()
----> 5 print(ds.att1)


AttributeError: att1
</pre>


}}}

#!div style="width:600px"


{{{
#!python
ds.att1 = "this is att1"
print(ds.att1)
this is att1
In [40]:

ds.att3 = "this is att3"


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-40-3f4ad9ce7b2c> in <module>()
----> 1 ds.att3 = "this is att3"


AttributeError: 'DemoSlots' object has no attribute 'att3'
</pre>


}}}

#!div style="width:600px"


{{{
#!python
class DerivedSlots(DemoSlots): pass
ds = DerivedSlots()
ds.att3 = "this is att3"


}}}

Méthodes statiques et méthodes de classe

#!comment
QUE SE PASSE T IL EN CAS D HERITAGE ET DE REDEFINITION D UNE METHODE ?

Imaginons que l’on veuille compter le nombre d’instances créées par une classe. Pour récupérer le nombre courant d’instances,on peut écrire une méthode qui ne se sert que de l’attribut de classe, et cette méthode devrait pouvoir être appelée via le nom de classe, sans passer par une instance, mais cela ne fonctionne pas à l’aide d’une implémentation « naive » telle que celle-ci 🙂

#!div style="width:600px"


{{{
#!python
class DemoComptage:
__nb_objets = 0
def init(self):
DemoComptage.nb_objets = DemoComptage.nb_objets+1
def nb_objets():
return __nb_objets

a = DemoComptage()
b = DemoComptage()
c = DemoComptage()

print(DemoComptage.nb_objets())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-225b71655857> in <module>()
     10 c = DemoComptage()
     11 
---> 12 print(DemoComptage.nb_objets())


TypeError: unbound method nb_objets() must be called with DemoComptage instance as first argument (got nothing instead)
</pre>


}}}

En effet, même si self n’est pas utilisé dans le corps d’une méthode, l’interpréteur Python exige qu’une méthode soit invoquée à travers une instance. L’attribut nb_objets de la classe est évidemment accessible à tous, donc il peut être lu directement par les clients, mais si on tient à préserver l’encapsulation, une simple fonction extérieure peut faire l’affaire 🙂

#!div style="width:600px"


{{{
#!python
class DemoComptage:
nb_objets = 0
def init(self):
DemoComptage.nb_objets = DemoComptage.nb_objets+1

def nb_objets():
return DemoComptage.nb_objets

a = DemoComptage()
b = DemoComptage()
c = DemoComptage()

print(nb_objets()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
3
</pre>

}}}

Cependant,pour satisfaire les programmeurs qui tiennent à localiser la fonction au sein de la classe, depuis Python 2.2, on peut définir des méthodes dites « statiques », qui peuvent s’invoquer sans passer par une instance 🙂

#!div style="width:600px"


{{{
#!python
class DemoComptage:
_nb_objets = 0
def init(self):
DemoComptage._nb_objets = DemoComptage._nb_objets+1
def nb_objets():
return DemoComptage._nb_objets
nb_objets = staticmethod(nb_objets)

a = DemoComptage()
b = DemoComptage()
c = DemoComptage()

print(DemoComptage.nb_objets()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
3
</pre>

}}}

Il existe également des méthodes dites « de classe »,qui recoivent en premier argument non pas l’instance courante, mais la classe courante.)

#!div style="width:600px"


{{{
#!python
class DemoClassMethod:
def m(cls,data):
print(cls,data)
m = classmethod(m)

DemoClassMethod.m("bonjour")


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
<class 'main.DemoClassMethod'> bonjour
</pre>

}}}

Décorateurs de fonctions

Les méthodes statiques et les méthodes de classes sont des cas particuliers de « décorateurs » de fonction.

Un décorateur est une fonction qui manipule une fonction. Il peut effectuer une action une fois pour toute, et renvoyer la fonction originale, ou bien renvoyer une nouvelle fonction qui effectuera des manipulations à chaque appel, avant de le répercuter à la fonction originale qui aura été mémorisée en interne.

Par exemple, lors de l’instruction nb_objets = staticmethod(nb_objets), on appelle le décorateur staticmethod, qui substitue à la fonction nb_objets() originale une autre fonction, dont le rôle sera, à chaque appel, de mettre de côté le premier argument (self) et de transmettre les autres à la fonction nb_objets() originale.

On peut maintenant définir plus facilement une décoration, à l’aide du caractère @ :

#!div style="width:600px"


{{{
#!python
class DemoDeco:
_nb_objets = 0
def init(self):
DemoDeco._nb_objets += 1
@staticmethod
def nb_objets():
return DemoDeco._nb_objets
@classmethod
def m(cls,data):
print(cls,data)

a = DemoDeco()
b = DemoDeco()
c = DemoDeco()

print(DemoDeco.nb_objets()


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
3
</pre>

}}}

#!div style="width:600px"


{{{
#!python
DemoDeco.m("bonjour")


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
<class 'main.DemoDeco'> bonjour
</pre>

}}}

On peut empiler autant de décorateurs que souhaités. Le code ci-dessous :

#!div style="width:600px"


{{{
#!python
@A @B @C
def f():
...


}}}

est l’équivalent de :

#!div style="width:600px"


{{{
#!python
def f():
...
f = A(B(C(f)))


}}}

On peut bien sur écrire ses propres décorateurs. On les implémente en général à l’aide d’une classe qui stocke la fonction décorée,et définit l’opérateur d’appel call. Par exemple, ci-dessous, une classe qui compte les appels à la fonction décorée 🙂

#!div style="width:600px"


{{{
#!python
class compteur:
def init(self,func):
self.func = func
self.count = 0
def call(self,*args):
self.count += 1
print('call {} to {}'.format(self.count,self.func.name))
self.func(*args)

@compteur
def bidon(texte):
print(texte)

bidon("bonjour")


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
call 1 to bidon
bonjour
</pre>

}}}

#!div style="width:600px"


{{{
#!python
bidon("bonsoir")


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
call 2 to bidon
bonsoir
</pre>

}}}

Enfin, notons qu’il existe des décorateurs permettant de simplifier la définition des propriétés d’une classe (attributs dont l’accès en lecture et/ou en écriture est confié à des méthodes). L’exemple de vecteur précédemment vu peut-être réécrit ainsi :

#!div style="width:600px"


{{{
#!python
class Vecteur:
def init(self,u=0,v=0):
self.x = u
self.y = v
@property
def x(self):
return self.__x
@x.setter
def x(self,u):
if (u<-1): self.__x = -1
elif (u>1): self.__x = 1
else: self.__x = u
@property
def y(self):
return self.__y
@y.setter
def y(self,v):
if (v<-1): self.__y = -1
elif (v>1): self.__y = 1
else: self.__y = v

v = Vecteur()
v.init(3, -4)
print(v.x,v.y)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
1 -1
</pre>

}}}

Hériter d’un type prédéfini

On peut hériter des types prédéfinis, comme de n’importe quelle autre classe. ATTENTION, dans l’exemple ci-dessous, comme on hérite d’une classe « non-modifiable » (« immutable »), on ne peut pas redéfinir init, qui intervient après a création de l’instance ; on est obligé de passer par une redéfinition de new.

#!div style="width:600px"


{{{
#!python
class FloatWithUnit(float):
def new(cls,value,unit):
instance = float.new(cls,value)
instance.unit = unit
return instance
def str(self):
return float.str(self)+" "+self.unit
def mul(self,other):
return FloatWithUnit(float.mul(self,other),self.unit+"*"+other.unit)

largeur = FloatWithUnit(2,"cm")
longueur = FloatWithUnit(5,"cm")
print(largeur*longueur)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
10.0 cm*cm
</pre>

}}}

Les classes de style ancien

Toutes les explications de cette formation concerne les classes de nouveau style, apparue avec 2.2, et qu’il faut utiliser à chaque fois que c’est possible. Comment savoir si une classe est d’ancien ou nouveau style ?

  • avant 2.2 : toutes les classes sont de stype ancien
  • de 2.2 à 2.x : les classes qui ont la classe object parmi leurs ancêtres sont de nouveau style.
  • à partir de 3 : toutes les classes sont de nouveau style

Un grand nombre de fonctionnalités avancées ne sont disponibles que pour les classes de nouveau style : super(), propriétés, encoches, méthodes statiques, décorateurs, héritage d’un type prédéfini…

Par ailleurs, en cas d’héritage en losange, les comportements son subtilement différents. Pour les classes de nouveau style, la recherche d’attribut se fait en profondeur d’abord, puis de gauche à droite, à une exception près : les classes dérivées sont toujours explorées avant leurs classes de base. Cette exception n’avait pas cours pour les classes de style ancien :

#!div style="width:600px"


{{{
#!python
class A: x = "A"
class B1(A): pass
class B2(A): x = "B2"
class C(B1,B2): pass

print(C.x)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
A
</pre>

}}}

En cas de nouveau style :

#!div style="width:600px"


{{{
#!python
class A: x = "A"
class B1(A): pass
class B2(A): x = "B2"
class C(B1,B2): pass

print(C.x)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
B2
</pre>

}}}

Pour éviter toute ambiguité, quel que soit le style de classe, on peut dire explicitement quelle est la méthode à utiliser :

#!div style="width:600px"


{{{
#!python
class A: x = "A"
class B1(A): pass
class B2(A): x = "B2"
class C(B1,B2): x = B2.x

print(C.x)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
B2
</pre>

}}}

Itérateurs

Aujourd’hui, avant de rechercher une méthode getitem, l’interpréteur Python cherche d’abord une méthode iter, qui peut implémenter des schémas d’itération plus complexes. La méthode iter est supposée retourné un objet « itérateur », sur lequel on va ensuite appeler la méthode next, qui renvoit un nouvel élément à chaque appel, et lève une exception StopIteration lorsqu’il n’y a plus d’éléments.

#!div style="width:600px"


{{{
#!python
class ReverseIterator:
def init(self, seq):
self.seq = seq
self.index = len(seq.data)
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.seq.data[self.index]

class Reverse:
def init(self, data):
self.data = data
def iter(self):
return ReverseIterator(self)

for char in Reverse('123'):
print(char)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
3
2
1
</pre>

}}}

Notre classe peut servir elle aussi dans tous les contextes d’itération.

#!div style="width:600px"


{{{
#!python
inverse = Reverse('123')
[c for c in inverse]


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
['3', '2', '1']
</pre>

}}}

#!div style="width:600px"


{{{
#!python
[c for c in inverse]


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
['3', '2', '1']
</pre>

}}}

#!div style="width:600px"


{{{
#!python
[c+d for c in inverse for d in inverse]


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
['33', '32', '31', '23', '22', '21', '13', '12', '11']
</pre>

}}}

Pour éviter d’avoir à écrire deux classes, on peut être tenté de dire qu’objet est son propre itérateur, et de placer la méthode next() dans la classe d’origine (exemple ci-dessous). Dans la pratique, c’est peu utile, car l’objet ne peut être itéré qu’une seule fois. On peut alors être tenté d’ajouter une méthode raz() qui remettrait l’index à 0, mais avec encore une limite : impossible de lancer deux itérations simultanées. Ne rusez pas : faites deux classes.

#!div style="width:600px"


{{{
#!python
class Reverse:
def init(self, data):
self.__data = data
self.__index = len(data)
def iter(self):
return self
def next(self):
if self.__index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]

inverse = Reverse('123')
print([c for c in inverse])
print([c for c in inverse])


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
['3', '2', '1']
[]
</pre>

}}}

#!div style="width:600px"


{{{
#!python
class Reverse:
def init(self, data):
self.__data = data
self.__index = len(data)
def iter(self):
return self
def next(self):
if self.__index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
def raz(self):
self.index = len(self.data)

inverse = Reverse('123')
print([c for c in inverse])
inverse.raz()
print([c for c in inverse])
inverse.raz()
print([c+d for c in inverse for d in inverse])


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
['3', '2', '1']
['3', '2', '1']
['32', '31']
</pre>

}}}

#!comment


Exercice

Écrire une classe qui permettra de gérer un fichier (dont le nom sera passé au constructeur). Elle devra comporter plusieurs méthodes qui réaliseront des opérations sur le fichier :

  • count : renvoie le nombre de lignes, de mots et de lettres.
  • head(n) : renvoie les n premières lignes.
  • tail(n) : renvoie les n dernières lignes.
  • find(str) : afficher toutes les lignes contenant la chaine str.
  • replace(str1, str2) : afficher à l’écran toutes les lignes du fichier, en remplaçant chaque occurrence de str1 par str2.
  • tout ce qui pourra vous passer par la tête (mais qui ne modifie pas le fichier lui-même).

Il faudra veiller à ne pas charger en mémoire tout le contenu du fichier, ni à le conserver ouvert trop longtemps.

Travaux Pratiques

1. Dotez la classe Expression d’une variable PI. Modifiez Expression.init() afin qu’elle reconnaisse la valeur PI, et essayez d’évaluer l’expression 2*PI avec votre calculatrice.
1. Définir Expression.slots et controllez son bon fonctionnement.
1. Protéger la valeur PI, supposée constante, en la rendant pseudo-privée et en fournissant une méthode statique de récupération de la valeur. Utilisez la notation « décorateur » pour cette méthode.
1. A la place de la méthode Calculatrice.getres(), ajouter une propriété res, accessible en lecture seule.

#!comment
1. Observez ce qui se passe si la classe Calculatrice n'hérite plus de la classe object.

© David Chamont, Laboratoire Leprince-Ringuet (LLR) / IN2P3 / CNRS, Ecole polytechnique / Université Paris-Saclaybr
© Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution – Partage dans les Mêmes Conditions 4.0 International [=#license]


Attachments