---
# System prepended metadata

title: CP Python S12 - Correction QBF

---

# CP Python S12 - Correction QBF

## Code de Sophie

```python=
class Item :

    def __init__(self,author,title,serial):
        """
        Méthode d'initialisation.
        @pre  author et title sont des valeurs de type String
              serial est un entier > 0
        @post Une instance de la classe est créée, et représente un objet ayant
              comme auteur author, comme titre title et comme numéro de série serial
        """
        self.__author = author
        self.__title = title
        self.__serial = serial

    def __str__(self):
        """
        Méthode de conversion en string.
        @pre  -
        @post le string retourné contient une représentation de cet objet sous la
              forme: [num série] Auteur, Titre
        """
        return "[" + str(self.__serial) + "]" + str(self.__author) + ", " + str(self.__title) 


class CD(Item):
    # Variable de classe qui représente serial 
    __serial = 100000

    def __init__(self, author, title, seconds):
      super().__init__(author, title)
      self.__duree = seconds
      CD.__serial += self.get_serial()

    @classmethod
    def get_serial (cls):
      return cls.__serial + 1

    def __str__(self):
      return "[" + str(self.__serial) + "]" + str(self.__author) + ", " + str(self.__title) + " (" + str(self.__duree) + " s" + ")"
```
 
### Tests
```python
item = Item("Guillaume & Sophie", "Python from A to Z", 101)
cd = CD("Radiohead","The Bends",2917)
cd2 = CD("The Geek","Python is cool",3045)
print("Test 1", str(item) == "[101] Guillaume & Sophie, Python from A to Z")
print("Test 2:", str(cd) == "[10000] Radiohead, The Bends (2917 s)")
print("Test 3:", str(cd2) == "[10001] The Geek, Python is cool (3045 s)")
```

### Erreurs
__1__ ![](https://i.imgur.com/e9ZRfwW.png)
=> Python râle car à la ligne 30, on fait appel à la méthode `__init__` de la classe-mère, `Item`, qui attend non pas 2, mais 3 arguments. Le 3e argument attendu est `serial`...Ca tombe bien, on a aussi besoin d'un numéro de série pour les CD ! Pourquoi ne pas y placer `CD__serial` ? 

```python
def __init__(self,author,title, duree):
  super().__init__(author,title,CD.__serial)
  # etc 
```
__2__ ![](https://i.imgur.com/D3ba8qY.png)
* Cool, notre premier test passe!
* Malheureusement une autre erreur avec `_CD__author` ("name mangling")...Ca ne vous rappelle rien ? Typiquement le genre d'erreur lorsqu'on ne parvient pas à accéder à des variables d'instance privées! Deux choix s'offrent à nous:
    1. Soit on définit des *méthodes accesseurs* dans la classe-mère (`author(self)` et `title(self)` et `serial(self)`) et on les réutilise dans la classe-fille (à la ligne 39, dans `__str__(self)`)
        ```python
        def author(self):
              return self.__author

        def title(self):
              return self.__title
    
        def serial(self):
              return self.__serial 
        ```
        ```python
        def __str__(self):
              return "[" + str(self.serial()) + "] " + str(self.author()) + ", " + str(self.title()) + " (" + str(self.__duree) + " s" + ")"
          ```
    2. Soit on se rend compte qu'au final les méthodes `__str__(self)` des classes mère et fille se ressemblent tout de même pas mal ?! On décide d'utiliser `super()` pour accéder directement à la méthode `__str__(self)` de la classe-mère (Item) et on la redéfinit (*overriding*) pour `CD` afin d'intégrer également la `duree`. 
        ```python
         def __str__(self):
              return super().__str__() + " ({} s)".format(self.__duree)
         ```
__3__
* Cool, tous nos tests s'éxecutent sans erreur! Malheureusement, le dernier ne passe pas...
* En printant la représentation de `cd` et `cd2` on a:
![](https://i.imgur.com/cRZZEUI.png)
* Etrange, on s'attend à ce que le 2e CD ait le numéro de série $10001$...
    ![](https://i.imgur.com/KcO8yCl.png)
    * Okay, ceci s'explique en décomposant `CD__serial += self.get_serial()`...A chaque nouveau CD, cela revient à dire: `CD.__serial = CD.__serial + CD.__serial +1`. Après 1 CD, on aurait `CD.__serial` = $20001$, après 2 CD, on aurait `CD.__serial` = $20001 + 20001 + 1$...Pas exactement ce qu'on veut 🤨 Pourquoi pas simplement: 
        ```python
        __serial = 10000

        def __init__(self,author,title, duree):
          super().__init__(author,title,CD.__serial)
          CD.__serial += 1
          self.__duree = duree 
        ```
* BINGO !

    ![](https://i.imgur.com/egQJScJ.png)
* __NB__ Dans cet exercice, on n'était pas obligé de définir la méthode de classe `get_serial(cls)`. Cela dit, cela peut être utile si l'on souhaite accéder directement à la valeur de `CD.__serial` en dehors de la définition de la classe `CD` :-) Une implémentation correcte serait:  
    ```python
    @classmethod
        def get_serial (cls):
              return cls.__serial 
    ```

## Correctif entier

```python
class Item :

    def __init__(self,author,title,serial):
        """
        Méthode d'initialisation.
        @pre  author et title sont des valeurs de type String
              serial est un entier > 0
        @post Une instance de la classe est créée, et représente un objet ayant
              comme auteur author, comme titre title et comme numéro de série serial
        """
        self.__author = author
        self.__title = title
        self.__serial = serial

    def __str__(self):
        """
        Méthode de conversion en string.
        @pre  -
        @post le string retourné contient une représentation de cet objet sous la
              forme: [num série] Auteur, Titre
        """
        ### À compléter ###
        return "[{}] {}, {}".format(self.__serial, self.__author, self.__title)

class CD(Item):
    __serial = 10000

    def __init__(self,author,title, duree):
      super().__init__(author,title,CD.__serial)
      CD.__serial += 1
      self.__duree = duree 

    def __str__(self):
      return super().__str__() + " ({} s)".format(self.__duree)
```