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