tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
ISIMA
  • twitter
  • linkedin
  • viadeo

First class

Date de première publication : 2022/09/28

Gérer une classe simple

L'idée est de savoir créer une classe simple avec des attributs et des méthodes, l'instancier et la faire vivre.

Une classe vide


class FirstClass:
    pass

first_class = FirstClass()
print(first_class)

<__main__.FirstClass object at 0x10dd71580>

Une classe simple

Voici une déclaration "classique" d'une classe avec un attribut et une méthode pour accéder à cet attribut


class Student:
    def __init__(self):
        self.name ="loic"
    
    def get_name(self):
        return self.name

Pour instancier la classe et manipuler l'attribut, directement ou par la méthode, cela donne le code suivant :


student = Student()
print(student.name)
print(student.get_name())
student.name = "yon"
print(student.get_name())
print(type(student.name))

Que se passe-t-il si on change de "type" pour l'attribut ?


student.name = 4
print(type(student.name))

Cela ne pose pas de problème

On peut vérifier que le mot self (qui n'est pas un mot-clé du langage) n'est qu'une convention :


class Student:
    def __init__(me):
        me.name ="encore"
    
    def get_name(me):
        return me.name

On va vérifier maintenant ce qui se passe si un attribut est utilisé sans être défini :


class Student:
    def get_name(self):
        return self.name

student=Student()
print(student.get_name())

On obtient alors le message suivant :


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in get_name
AttributeError: 'Student' object has no attribute 'name'

On peut alors - mais est-ce une bonne idée - le définir en dehors de __init__() et cela va marcher !


class Student:
    def get_name(self):
        return self.name

student=Student()
student.name = "moi"
print(student.get_name())

Comme quoi, on peut faire n'importe quoi !!!

Variations sur le constructeur

Plusieurs constructeurs ?

Que se passe-t-il si on déclare plusieurs constructeurs pour une classe ?


class Student:
    
    def __init__(self, name):
        self.name = name

    def __init__(self):
        self.name ="noname"
    
    def get_name(self):
        return self.name

et1 = Student()
et2 = Student("essai")

Il n'y a pas de message d'erreur à la lecture du script mais seul le dernier constructeur est pris en compte, donc et1 est instancié mais pas et2.

Comment avoir "plusieurs" constructeurs ?

Il faut utiliser le système de valeur par défaut pour les paramètres...

Si l'on teste le code ci-dessous :


class Student:
    
    def __init__(self, name):
        self.name = name
    
    def get_name(self):
        return self.name

et = Student()
print(et.get_name())

on obtient naturellement le message suivant :


TypeError: __init__() takes 1 positional argument but 2 were given

On peut alors donner une valeur par défaut au paramètre name, par exemple :


def __init__(self, name = None): # ou toute autre valeur
    self.name = name

Que se passe-t'il si un argument avec une valeur par défaut est placé avant un autre argument ?


def __init__(self,firstname=None, name):
    self.firstname = firstname
    self.name = name

SyntaxError: non-default argument follows default argument

On pourra également avoir des méthodes de classes.

Encapsulation

Visibilité et limites

La visibilité en Python est une des plus grosses blagues qui soit ! Y en a pas !! On va tout de même tester les recommandations du PEP8 avec dans l'ordre

Pour chacune des variantes, on peut tester, qu'en fait, ce n'est qu'une CONVENTION !


class Student:
    def __init(self)__:
        self.name = "loic"
    
    def print(self)
        print(self.name)

student = Student()
print(student.name)

class Student:
    def __init(self)__:
        self._name = "loic"
    
    def print(self)
        print(self._name)

student = Student()
print(student.name)
print(student._name)

class Student:
    def __init(self)__:
        self.__name = "loic"
    
    def print(self)
        print(self.__name)
student = Student()
print(student.name)
print(student.__name)
print(dir(student)) # et pas dir(Student)
print(student._Student__name)

On peut voir que __name a été renommé et que ce nom est toujours accessible de l'extérieur

essayez ensuite :


student.__name = "et alors"
student.print()
print(student.__name)
print(dir(student))

L'ajout d'attribut dynamique fait que l'on a créé un nouvel attribut __name et donc après, il est accessible et est différent de l'attribut défini par le constructeur.

Intérêt de l'encapsulation

Coder la petite histoire du point et de ses coordonnées ...

Attributs et méthodes de classe

Syntaxe

Pour définir un attribut de classe, il suffit de l'initialiser dans la classe :


class Student:
    counter = 0

    def __init(self,  name):
       self.name = name
       # print(counter)

et1 = Student("moi")
et2 = Student("encore")

La question se pose de comment le manipuler dans une méthode telle que le constructeur (par exemple, incrémenter et afficher la valeur)


class Student:
    counter = 0

    def __init(self,  name):
       self.name = name
       Student.counter +=1
       print(Student.counter)

    @classmethod
    def get_counter(cls):
        return cls.counter

et1 = Student("moi")
et2 = Student("encore")
print(Student.get_counter())
#print(et1.get_counter())

Des constantes ?

Les constantes n'existent pas en python. Le guide de style permet de les répérer en les écrivant en majuscules.

On peut utiliser des outils tels mypy pour spécifier les constantes et faire de la vérification AVANT l'exécution du code


from typing import Final
a: Final = 1 

a = 2

mypy est utilisable pour faire de la vérification de "type" sur nos codes :

class Student: counter : int = 0 def __init(self, name : str) -> None : self.name = name Student.counter +=1 print(Student.counter) @classmethod def get_counter(cls) -> None: return cls.counter

Il est légal de ne pas spécifier de type pour self et cls

Créer des "constructeurs" multiples ?

Si vous reprenez l'exemple de la classe Point développée pour l'encapsulation, on veut pouvoir créer des points de deux manières, soit en connaissant un couple (x, y), soit un couple (r,t). On a aucun moyen de faire cela en python mais pour être honnête, on ne pourrait pas le faire non plus avec un langage typé car les types sont les mêmes ...


class Point:

    @classmethod
    def CreateCartesian(cls, x, y):
       return cls(x,y)
    
    @classmethod
    def CreatePolar(cls, r, t):
       return cls(r*cos(t),r*sin(t))

Méthodes statiques

Après les méthodes d'instance, les méthodes classiques, il reste un dernier type de méthode à tester : les méthodes statiques. Elles n'ont besoin ni d'instance, ni de classe. On peut considérer que ce sont des fonctions rangées dans une classe (comme un espace de nommage)


class Student:
    @staticmethod
    def calcul(x, y):
        return x*x+y*y

L'usage du mot-clé static en python est différent des langages comme le C++ ou le Java.

J'ai déjà vu dans des codes une méthode statique manipuler un attribut de classe sous la forme Classe.attribut mais je ne suis pas sur que ce soit une bonne pratique !