453 lines
9.7 KiB
Plaintext
453 lines
9.7 KiB
Plaintext
|
Définir et manipuler des classes
|
||
|
=================================
|
||
|
|
||
|
.. glossary::
|
||
|
|
||
|
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 :term:`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
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
|
||
|
.. glossary::
|
||
|
|
||
|
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, :term:`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()
|
||
|
|
||
|
:term:`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 !
|
||
|
|
||
|
.. function:: type (objname)
|
||
|
|
||
|
:param 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.
|
||
|
|
||
|
.. todo:: travailler l'héritage, l'aggrégation, la délégation
|
||
|
|
||
|
Voici un exemple de classe `Voiture` :
|
||
|
|
||
|
.. literalinclude:: snippets/heritage.py
|
||
|
:pyobject: Voiture
|
||
|
|
||
|
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'
|
||
|
|
||
|
.. todo:: faire des traitements dans l'init
|
||
|
|
||
|
- héritage (est une sorte de)
|
||
|
- polymorphisme : un objet apparait comme une instance d'une classe parente
|
||
|
|
||
|
.. literalinclude:: snippets/heritage.py
|
||
|
:pyobject: Turbo
|
||
|
|
||
|
>>> 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 :mod:`abc`
|
||
|
(abstract base classes).
|
||
|
|
||
|
.. literalinclude:: snippets/heritage.py
|
||
|
:pyobject: achete_voiture
|
||
|
|
||
|
tout le code :
|
||
|
|
||
|
.. literalinclude:: snippets/heritage.py
|
||
|
|
||
|
:download:`télécharger le code <snippets/heritage.py>`
|
||
|
|
||
|
- **l'aggrégation**
|
||
|
|
||
|
un attribut est lui-même un objet (ce qui est fréquent en python)...
|
||
|
|
||
|
.. literalinclude:: snippets/aggregation.py
|
||
|
|
||
|
- **la délégation**
|
||
|
|
||
|
la fonction "property" est un élément du design de python lui-même
|
||
|
|
||
|
.. function:: property()
|
||
|
|
||
|
les patrons de conception
|
||
|
---------------------------
|
||
|
|
||
|
- le patron **factory**
|
||
|
|
||
|
.. literalinclude:: snippets/patrons.py
|
||
|
|
||
|
:download:`télécharger usine (factory) <snippets/patrons.py>`
|
||
|
|
||
|
- le patron **wrapper**
|
||
|
|
||
|
:download:`télécharger wrapper <snippets/wrap.py>`
|
||
|
|
||
|
.. literalinclude:: snippets/wrap.py
|
||
|
|
||
|
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**
|
||
|
|
||
|
:download:`télécharger iterable <snippets/iterable.py>`
|
||
|
|
||
|
.. literalinclude:: snippets/iterable.py
|
||
|
|
||
|
- le patron **decorateur**
|
||
|
|
||
|
:download:`télécharger decorateur <snippets/decorateur.py>`
|
||
|
|
||
|
.. literalinclude:: snippets/decorateur.py
|
||
|
|
||
|
>>> 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
|
||
|
|