Définir et manipuler des classes¶
- objet
- Un object est une entité possédant un type, un état, et un comportement. Un object correspondant généralement à une entité du monde réel, mais cette entité peut être abstraite. On parle aussi d”instance.
état d’un objet : ensemble de propriétés auxquelles sont associées des valeurs.
Les variables de l’objet sont appelées des attributs. le comportement d’un objet :
- des actions effectuées sur l’objet
- des appels faits sur l’objet
envois de messages à l’objet = appel de méthodes
programmation objet (première approche)¶
- le type et le protocole d’un objet sont définis par sa classe
- une classe possède un ensemble d’attributs et de méthodes
deux relations possibles¶
- heritage
- relation « est une espèce de » utilisée entre une classe et une autre classe
- instantiation
- relation « est une instance de » entre un objet et une classe
- est une instance de (objets par rapport à classe)
- est une espèce de (classe par rapport à classe, heritage)
instance
- définition d’une classe
- instance de classe : on peut créer des objets à partir d’un type « classe » (une classe est instanciable)
>>> class A:
... pass
...
>>> a = A()
heritage : notation en python
>>> class A: pass
...
>>> class B(A): pass
...
>>> b = B()
>>> type(b) == B
>>> isinstance(b, A) == True
possibilité en python d’héritage multiple:
class A(B, C): pass
attribut d’objets et de classes¶
>>> o = object()
>>> o
<object object at 0x7f77c9cda0d0>
>>> class C(object): pass
...
>>> class C: pass
...
>>> c = C()
>>> c.a = 3
>>> c.a
3
>>> vars(c)
{'a': 3}
>>> c.__dict__
{'a': 3}
>>> C.__dict__
{'__module__': '__main__', '__doc__': None}
>>> C.c = 5
>>> C.__dict__
{'c': 5, '__module__': '__main__', '__doc__': None}
>>> c.c
5
>>> c.z = 3
>>> c.z
3
>>> c.__dict__
{'a': 3, 'z': 3}
>>> C.__dict__
{'c': 5, '__module__': '__main__', '__doc__': None}
>>> class MaKlass:
... def unefonction(self, x):
... print x
...
>>> MaKlass.__dict__
{'__module__': '__main__', '__doc__': None, 'unefonction': <function unefonction at 0x7f77c5b0c488>}
>>> k = MaKlass()
>>> k.__dict__
{}
>>> def autrefonc(self, x)
File "<stdin>", line 1
def autrefonc(self, x)
^
SyntaxError: invalid syntax
>>> def autrefonc(self, x):
... print x
...
>>> k.autrefonc = autrefonc
>>> k.__dict__
{'autrefonc': <function autrefonc at 0x7f77c5b0c500>}
>>> MaKlass.__dict__
{'__module__': '__main__', '__doc__': None, 'unefonction': <function unefonction at 0x7f77c5b0c488>}
>>> MaKlass.unefonction(k, "toto")
toto
>>> k.unefonction("toto")
toto
>>> k.__dict__
{'autrefonc': <function autrefonc at 0x7f77c5b0c500>}
>>> MaKlass.toto = "test"
>>> k.__dict__
{'autrefonc': <function autrefonc at 0x7f77c5b0c500>}
>>> k.toto
'test'
>>>
le __dict__ avec l’héritage de classe¶
>>> class A(object): pass
...
>>> A.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})
>>> class B(A):
... b = 3
...
>>> class C(B):
... c = 2
...
>>> c = C()
>>> o = C()
>>> o.__dict__
{}
>>> o.c
2
>>> o.b
3
>>> o.__class__
<class '__main__.C'>
>>> o.__class__.__dict__
dict_proxy({'__module__': '__main__', 'c': 2, '__doc__': None})
>>>
method resolution object¶
>>> class A(object): pass
...
>>> A.__mro__
(<class '__main__.A'>, <type 'object'>)
>>> class B(A): pass
...
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
>>> class C(A, B): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution
order (MRO) for bases A, B
>>>
introspection contre encapsulation¶
voir un objet comme un espace de nommage. C’est plus agile.
attributs et méthodes vus comme des ajouts dans l’espace de nommage
>>> a.a = 2
>>> def function(x):
... print x
...
>>> a.f = function
>>> a.f("hello")
hello
>>>
la nécessité d’un design objet¶
affichage d’une calculette, il faut créer un type Touche qui contient deux désignations : Chiffre et operation:
type operation = Plus | Moins | Divise
type touche = Chiffre of int | Memoire | Op of operation
soit, on définit un type touche, soit on ne définit pas ce type:
type operation = Plus | Moins | Divise
type memoire = Memoire
type chiffre = Chiffre
- les structures de données (int, str, list, dict…) : types de base
- les structures de données utilisateur : les classes !
-
type
(objname)¶ Paramètres: objname – l’objet dont on veut connaître le type
Manipulations sur les classes et les objets¶
En python un type et une classe c’est la même chose. Une classe
est un type standard. En python tout est objet, et tout provient d’un seul objet
(qui s’appelle object
).
- encapsulation (cacher les attributs (variables d’état)) d’un objet.
- interfaces : chaque aspect d’une classe peut être vu comme une interface.
Une interface décrit un ensemble de comportements. on peut considérer une interface comme un protocole d’utilisation d’un objet dans un contexte donné.
on peut alors créer des outils qui sauront traiter n’importe quel objet pourvu qu’il respecte une ensemble d’interfaces.
À faire
travailler l’héritage, l’aggrégation, la délégation
Voici un exemple de classe Voiture :
class Voiture(Prix, Turbo):
def __init__(self, constructeur, vitesse_max=160):
self.constructeur = constructeur
self.vitesse_max = vitesse_max
def roule(self):
return "vroummm"
def signaletique(self):
return "constructeur : {0}, vitesse_max {1}".format(self.constructeur,
self.vitesse_max)
j’instancie ma classe Voiture :
>>> ma_voiture = Voiture("ma_marque", "150km/h")
>>> ma_voiture.roule()
'vroummm'
>>> ma_voiture.signaletique()
'constructeur : ma_marque, vitesse_max 150km/h'
À faire
faire des traitements dans l’init
- héritage (est une sorte de)
- polymorphisme : un objet apparait comme une instance d’une classe parente
class Turbo(object):
def turbo(self):
return "VRRRRROUUUMMM"
>>> v = DoDoche("marque", 160)
>>> v.get_prix()
'7000'
>>> isinstance(v, Prix)
True
>>>
mais l’objet conserve son identité :
>>> type(v)
<type 'Voiture'>
la fonction achete_voiture()
sera appelée indépendamment du type de l’objet,
du moment que l’objet a une méthode get_prix(), c’est le duck typing, qu’il
est préférable de ramener au polymorphisme d’objet, ou bien utiliser les abc
(abstract base classes).
def achete_voiture(voiture):
if not hasattr(voiture, "get_prix"):
raise TypeError("pas le bon type")
return "prix de la voiture: {0} euros".format(voiture.get_prix())
tout le code :
class Turbo(object):
def turbo(self):
return "VRRRRROUUUMMM"
class Prix(object):
def get_prix(self):
raise NotImplementedError
class Voiture(Prix, Turbo):
def __init__(self, constructeur, vitesse_max=160):
self.constructeur = constructeur
self.vitesse_max = vitesse_max
def roule(self):
return "vroummm"
def signaletique(self):
return "constructeur : {0}, vitesse_max {1}".format(self.constructeur,
self.vitesse_max)
class DoDoche(Voiture):
def get_prix(self):
return "4000"
def achete_voiture(voiture):
if not hasattr(voiture, "get_prix"):
raise TypeError("pas le bon type")
return "prix de la voiture: {0} euros".format(voiture.get_prix())
- l’aggrégation
un attribut est lui-même un objet (ce qui est fréquent en python)…
class A:
pass
class B:
pass
a = A()
a.b = B()
- la délégation
la fonction « property » est un élément du design de python lui-même
-
property
()¶
les patrons de conception¶
- le patron factory
class NotFoundError(Exception):
pass
class MaClasse:
pass
class MaClasseDeux:
pass
binding = dict(un=MaClasse, deux=MaClasseDeux)
def ma_factory(key):
if key in binding:
return binding[key]()
else:
return NotFoundError("keskece?")
- le patron wrapper
class Wrap(object):
def __init__(self, name, wrap):
self.slots = ('_name', '_w')
self._name = name or "wrapped_element"
self._w = wrap
def __getattr__(self, name):
if name in self.slots:
return getattr(self, name)
else:
return getattr(self._w, name)
# def get_w(self, name):
# return getattr(self._w, name)
# def set_w(self, name, value):
# return setattr(self._w, name, value)
# _w = property (get_w, set_w)
def __repr__(self):
return "[W_Element %s]"% repr(self._name)
exemple d’utilisation de Wrap()
>>> class O:
... pass
...
>>> o = O()
>>> o.a = "blah"
>>>
>>> from wrap import Wrap
>>> w = Wrap("monwrap", o)
>>> w._name
'monwrap'
>>> w._w
<__main__.O instance at 0x7fbf177aaa28>
>>> w._w.a
'blah'
>>> w.a
'blah'
>>> w.u
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "wrap.py", line 11, in __getattr__
return getattr(self._w, name)
AttributeError: O instance has no attribute 'u'
>>>
- le patron de conception itérable
liste = ['blah', 'blih', 'bluh']
iterateur = liste.__iter__()
print iterateur.next()
print iterateur.next()
print iterateur.next()
print iterateur.next()
#Traceback (most recent call last):
# File '<stdin>', line 1, in <module>;
#StopIteration
class Iterateur:
i = 0
def next(self):
if self.i <= 10: raise StopIteration
self.i += 1
return 2**self.i
def __iter__(self): return self
iterateur = Iterateur()
for i in iterateur: print i
- le patron decorateur
def helloworld(ob):
print "Hello world"
return ob
@helloworld
def myfunc():
print "my function"
myfunc()
print myfunc
>>> def deco(func):
... func.attr = 'decorated'
... return func
...
>>> @deco
... def f(): pass
...
>>> f.attr
'decorated'
>>>
autre exemple : les méthodes statiques
>>> class A(object):
... @staticmethod
... def my_class_method(cls):
... # do stuff here
métaclasses¶
>>> class A:
... pass
...
>>> type(A)
<type 'classobj'>
>>> class B(object): pass
...
>>> type(B)
<type 'type'>
>>> help(type)
>>> C = type('C', (), {})
>>> C
<class '__main__.C'>
>>>
>>> type(object)
<type 'type'>
>>> type(type)
<type 'type'>
>>> isinstance(type, object)
True
>>> isinstance(object, type)
True
>>>
class MaMetaClasse(type):
"""Exemple d'une métaclasse."""
def __new__(metacls, nom, bases, dict):
"""Création de notre classe."""
print("On crée la classe {}".format(nom))
return type.__new__(metacls, nom, bases, dict)
class MaClasse(object):
__metaclass__ = MaMetaClasse
exemple : forcer l’implémentation d’un singleton avec les métaclasses
class Singleton(type):
instance = None
def __call__(cls, *args, **kw):
if not cls.instance:
cls.instance = super(Singleton, cls).__call__(*args, **kw)
return cls.instance
class ASingleton(object):
__metaclass__ = Singleton
a = ASingleton()
b = ASingleton()
assert a is b
print(a.__class__.__name__, b.__class__.__name__)
class BSingleton(object):
__metaclass__ = Singleton
c = BSingleton()
d = BSingleton()
assert c is d
print(c.__class__.__name__, d.__class__.__name__)
assert c is not a