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
- Précéder le nom de l'attribut par un underscore pour donner
_name
ou_methode()
- Précéder le nom de l'attribut par un double underscore pour donner
__name
ou__methode()
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.counterIl 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 !