• 4 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 02/05/2018

Apprivoisez les décorateurs

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

 

Dans ce chapitre nous parlons des décorateurs. Mais qu'est-ce que cela veut dire ?

Vous en avez déjà probablement croisé ! Un décorateur ressemble à ceci : @decorator. Si vous avez suivi mon précédent cours sur la Programmation Orientée Objet, vous reconnaissez aisément @classmethod et @property dans cette syntaxe. Nous couvrons tout cela dans ce chapitre !

 

Les décorateurs 101

C'est bien joli, mais à quoi ça sert ?

Un décorateur permet de modifier le comportement d'une fonction. Il commence par un @ suivi de lettres ou de chiffres. Il se place sur la ligne précédant la définition d'une fonction. Comme ceci :

@decorator
def karadoc():
    print("Le gras, c'est la vie.")

 

Python intègre de nombreux décorateurs standards mais vous pouvez également en définir vous-même. Pourquoi ? Car un décorateur est simplement une fonction qui prend en paramètre une fonction et renvoie une (autre) fonction.

def decorator(func):
    def inner():
        # do something with the function
        return func()
    return inner

 

Dans cet exemple très générique, nous voyons que le décorateur définit une nouvelle fonction qui va renvoyer la fonction décorée. En quoi est-ce intéressant ? Cela permet de réaliser des opérations avec la fonction décorée : la modifier, l'étendre, ...

En voici une illustration :

def e_t(func):
    def inner():
        print("Maison... Téléphone... Maison...")
        return func()
    return inner

@e_t
def gertie():
    print("Je lui ai appris à parler ! Ecoute !")

 

Cela permet de faire le dialogue complet...

def e_t(func):
    def inner():
        print("Maison... Téléphone... Maison...")
        return func()
    return inner

@e_t
def gertie():
    print("Je lui ai appris à parler ! Ecoute !")


@e_t
def elliott():
    print("Il veut rentrer chez lui !")
>>> def e_t(func):
...     def inner():
...         print("Maison... Téléphone... Maison...")
...         return func()
...     return inner
...
>>> @e_t
... def gertie():
...     print("Je lui ai appris à parler ! Ecoute !")
...
Maison... Téléphone... Maison...
Je lui ai appris à parler ! Ecoute !
>>>
... @e_t
... def elliott():
...     print("Il veut rentrer chez lui !")
...
Maison... Téléphone... Maison...
Il veut rentrer chez lui !

 

Sous le capot

Il n'y a pas de magie. Ce @ peut vous sembler abscond mais il s'agit d'un raccourci vers une autre syntaxe que vous connaissez très bien.

def e_t(func):
    def inner():
        print("Maison... Téléphone... Maison...")
        return func()
    return inner

@e_t
def gertie():
    print("Je lui ai appris à parler ! Ecoute !")

Est la même chose que :

def e_t(func):
    def inner():
        print("Maison... Téléphone... Maison...")
        return func()
    return inner

def gertie():
    print("Je lui ai appris à parler ! Ecoute !")

gertie = e_t(gertie)

Vous pourriez également créer un décorateur qui affiche les erreurs en les loguant grâce à la librairie   logging. Il existe de nombreuses utilisations différentes des décorateurs !  

 

Créer un décorateur plus générique

Prenons un autre exemple concret que certains d'entre vous connaissent déjà.

class CoffeeMachine():
    
    water_level = 100
    
    def _start_machine(self):
      # Start the machine
      if self.water_level > 20:
          return True
      else:
          print("Please add water!")
          return False
          
    def __boil_water(self):
        return "boiling..."
        
    def make_coffee(self):
        # Make a new coffee!
        if self._start_machine():
            self.water_level -= 20
            print(self.__boil_water())
            print("Coffee is ready!")


machine = CoffeeMachine()
for i in range(0, 5):
    machine.make_coffee()

print("Make Coffee: Public", machine.make_coffee())
print("Start Machine: Protected", machine._start_machine())
print("Boil Water: Private", machine._CoffeeMachine__boil_water())

 

Les trois print() en fin de code ont pour objectif d'afficher un message et d'exécuter les méthodes. Vous pourriez créer un décorateur qui aurait pour simple mission d'imprimer le nom de la méthode avant son exécution, comme ceci :

def name(func):
    def inner(*args, **kwargs):
        print('Running this method:', func.__name__)
        return func(*args, **kwargs)
    return inner


class CoffeeMachine():
    
    water_level = 100
    
    @name   
    def _start_machine(self):
      # Start the machine
      if self.water_level > 20:
          return True
      else:
          print("Please add water!")
          return False
    
    @name
    def __boil_water(self):
        return "boiling..."
    
    @name
    def make_coffee(self):
        # Make a new coffee!
        if self._start_machine():
            self.water_level -= 20
            print(self.__boil_water())
            print("Coffee is ready!")


machine = CoffeeMachine()
for i in range(0, 5):
    machine.make_coffee()

machine.make_coffee()
machine._start_machine()
machine._CoffeeMachine__boil_water()

 

Que se passe-t-il ?

  • def name(func): Définition d'une nouvelle fonction qui prend la fonction décorée en paramètre.

  • def inner(*args, **kwargs): A l'intérieur, définition d'une nouvelle fonction.

  • print('Running this method:', func.__name__) : Première instruction : imprimer un message ainsi que le nom de la fonction.

  • return func(*args, **kwargs) : Deuxième instruction : exécuter la fonction décorée.

 

Pourquoi passer en paramètres *args et **kwargs?

Un décorateur doit être le plus générique possible. Il ne connaît pas la signature de l'objet à décorer, autrement dit il ne connaît pas son comportement. Est-ce que l'objet décoré prend un ou plusieurs paramètres ? Le décorateur ne le sait pas et n'a, d'ailleurs, aucun intérêt à le savoir. Sa seule responsabilité est de réaliser une action avec cet objet et de le renvoyer si nécessaire. C'est pourquoi nous indiquons   *args   et   **kwargs.

 

Voici ce qui est alors affiché dans la console :

>>> machine = CoffeeMachine()
>>> for i in range(0, 5):
...   machine.make_coffee()
...
Running this method: make_coffee
Running this method: _start_machine
Running this method: __boil_water
boiling...
Coffee is ready!
Running this method: make_coffee
Running this method: _start_machine
Running this method: __boil_water
boiling...
Coffee is ready!
Running this method: make_coffee
Running this method: _start_machine
Running this method: __boil_water
boiling...
Coffee is ready!
Running this method: make_coffee
Running this method: _start_machine
Running this method: __boil_water
boiling...
Coffee is ready!
Running this method: make_coffee
Running this method: _start_machine
Please add water!
>>> machine.make_coffee()
Running this method: make_coffee
Running this method: _start_machine
Please add water!
>>> machine._start_machine()
Running this method: _start_machine
Please add water!
False
>>> machine._CoffeeMachine__boil_water()
Running this method: __boil_water
'boiling...'
>>>

 

A vous de jouer !

Cliquez sur ce lien

 

Code du chapitre

Nous avons intégré, dans le code de ce chapitre, de nombreux exemples de décorateurs. Découvrez-les tout de suite ! 

Exemple de certificat de réussite
Exemple de certificat de réussite