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())

télécharger le code

  • 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?")
        
        

télécharger usine (factory)

  • le patron wrapper

télécharger 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

télécharger iterable

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

télécharger 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