formations/algo/poo/cours/classes.txt
2019-03-25 13:14:17 +01:00

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