Mis a jour le 2025-04-14, 12:10

Classes

Les bases

namespaces et scopes :
  • namespaces : en fait, ce sont des python dictionaries. les noms qui ne sont pas dans des modules sont en fait dans l'espace de nom __main__ (et les noms built-ins sont dans __builtin__).
  • 3 scopes sont recherchés successivement : local scope (local à la fonction), global scope (global au module) et built-in (noms built-in).
Définition de classe :
class MyClass:
    "a simple class"
    n = 0
    def myMethod(self, a):
        return 2 * a * self.myField
  
le code doit être executé avant que la classe ne soit définie (la définition peut très bien être à l'intérieur d'une fonction.
Constructeur : s'il existe, il doit s'appeler __init__ :
  • def __init__(self, arg):
        self.myField = arg
        
  • en fait, __init__ se contente d'initialiser un objet déjà alloué (l'allocation se faisant dans la méthode __new__), mais le rôle d'initialisation, c'est le rôle en général d'un constructeur.
Définition d'une méthode :
  • le premier argument doit toujours être l'objet lui-même et par convention, on l'appelle toujours self (mais c'est une convention seulement).
Instanciation d'une classe :
  • var = MyClass() : constructeur sans arguments.
  • var = MyClass('toto', 4) : constructeur avec arguments.
Appel d'un champ ou d'une méthode : si x est de la classe MyClass :
  • tous les champs sont publics et toutes les méthodes sont publiques.
  • on peut faire référence à x.myField même si myField n'a jamais été déclaré comme champ dans la classe !
  • on peut détruire aussi un champ en faisant del x.myField.
  • on ne peut faire référence a x.myMethod() que si myMethod est une fonction définie dans la classe (avec comme premier argument self).
  • x.myMethod(arg) est équivalent à MyClass.myMethod(x, arg).
  • on peut faire myMethod2 = x.myMethod; myMethod2() si on veut affecter la méthode (avec son objet) à une variable avant de l'appeler !
  • attention : les attributs de champs peuvent surcharger les attributs de type methode !
  • une méthode peut appeler une autre méthode avec self.myOtherMethod()
decorateur @property : permet de convertir une fonction en propriété, pour y accéder comme si c'était un champ :
class C:
    def __init__(self, a):
        self.a = a

    @property
    def A(self):
        return self.a
  
on peut alors faire obj = C(3); obj.A (au lieu de obj.A()) pour accéder au champ a.
On ne peut pas avoir plusieurs méthodes de même nom et de signatures différentes.
Pour voir le constructeur d'une classe MyClass qui est définie dans le module (fichier) MyClass (sans avoir besoin de le qualifier complètement), faire :
from MyClass import MyClass
(mais il peut aussi être défini dans un fichier qui ne s'appelle pas du nom de la classe, par exemple myModule et alors il faut faire : from myModule import MyClass

Membres privés

Membres privés :
  • tout champ ou méthode qui commence par 2 '_' et se termine au plus un '_' est privé.
  • exemples : __myField, __myField_, __myMethod, __myMethod sont privés (alors que __myField__ ou __myMethod__ ne le sont pas).
  • En fait, __myField et __myMethod sont remplacés par _MyClass__myField et _MyClass__myMethod et c'est pour ça qu'ils ne sont pas directement accessibles, c'est juste leur nom qui est changé ! On peut quand même y accéder avec le nouveau nom, mais évidemment, il ne faut pas le faire (sauf pour du test)
  • les membres privés sont pareillement inaccessible dans une classe dérivée.
Pour avoir le nom de la classe à partir d'une instance : myObj.__class__.__name__

Héritage et reflexion

Héritage :
  • class DerivedClass(BaseClass)
  • les méthodes sont "virtuelles" : si objet de la classe de base est en fait de la classe dérivée et appelle une méthode definie dans la classe de base et surchargée dans la classe dérivée, c'est la méthode de la classe dérivée qui est appelée.
  • dans la classe dérivée, on peut faire appel malgré tout à la classe de base avec BaseClass.myMethod(self, args)
python supporte l'héritage multiple :
  • class DerivedClass(BaseClass1, BaseClass2)
  • ordre de recherche des méthodes : depth-first, left-to-right (d'abord dans BaseClass1, puis dans ses classes de base, puis dans BaseClass2, puis dans ses classes de base).
Classe object :
  • c'est la classe de base des nouvelles classes (qui incluent les dictionnaires, les listes, les types primitifs, ...)
  • cette classe fournit en standard un certain nombre de champs et méthodes.
  • si on hérite d'aucune autre classe, préférer hériter de object.
Fonctions pour tester l'appartenance aux classes :
  • isinstance(obj, int) : renvoie True si l'objet est de la classe indiquée ou d'une classe dérivée.
  • issubclass(bool, int) : renvoie True si la classe indiquée (ici bool) est dérivée de la 2ème classe indiquée.
Pour avoir le nom de la classe d'un objet :
  • myObject.__class__ : donne la classe de l'objet (l'objet classe).
  • myObject.__class__.__name__ : donne le nom de la classe de l'objet (une string).
Pour trouver la hiérarchie des classes pour un objet donné :
  • myObject.__class__ : donne la classe de l'objet.
  • myObject.__class__.__bases__ : donne les classes de base de la classe de l'objet.
  • on peut continuer comme cela pour remonter à la classe de base initiale : myObject.__class__.__bases__[0].__base__[0] par exemple.
  • myObject.__class__.__mro__ : donne la liste des classes auxquelles l'objet appartient (MRO : Method Resolution Order).
On peut accéder par noms aux champs et méthodes d'un objet :
  • getattr(myObj, 'myField') : permet d'accéder au champ myField de l'objet myObj, donc équivalent à myObj.myField.
  • getattr(myObj, 'myMethod') : permet d'accéder à la méthode myMethod de l'objet myObj, donc équivalent à myObj.myMethod.
  • getattr(myObj, 'myField2', 'abc') : si le champ myField2 n'existe pas, renvoie 'abc' (marche aussi avec une méthode).
  • si myMethod est une méthode de MyClass, alors, MyClass.myMethod est une méthode "unbound" : pas encore liée à un objet, et si on veut l'appeler, il faut donner l'objet en premier argument.
  • si myMethod est une méthode de MyClass et myObj un objet de la classe MyClass, alors, myObj.myMethod est une méthode "bound" : elle est liée à l'objet, et si on veut l'appeler, il ne faut pas donner l'objet en premier argument, car il est déjà pris en compte !
On peut setter un champ avec : setattr(myObj, 'myField', 'myValue').
getter/setter générique d'une classe : permet d'exécuter du code quand on fixe un attribut de l'objet :
class MyClass:
    def __init__(self):
        pass

    def __getattr__(self, name):
        return self.__dict__[f'_{name}']

    def __setattr__(self, name, value):
        if name not in ['property1', 'property2']:
            raise RuntimeError('Unknown property ' + name)
        self.__dict__[f'_{name}'] = value

myObj = MyClass()
myObj.property1 = 45
print(myObj.property1)
  

Champs et méthodes statiques

Champs statique d'une classe :
  • on le définit de la façon suivante :
    def MyClass:
        myField = 5
        
  • on l'utilise à l'extérieur et à l'intérieur de la classe par MyClass.myField.
  • attention : on peut aussi accéder au champ avec un objet de la classe, mais ce n'est pas la même variable !
    obj = MyClass()
    print(obj.myField) # imprime 5
    obj.myField = 7
    print(obj.myField) # imprime maintenant 7
    print(MyClass.myField) # imprime toujours 5 !
        
Méthode statique d'une classe :
  • on la définit de la façon suivante :
    def MyClass:
        @staticmethod
        def myMethod():
            ...
        
  • on l'utilise à l'extérieur et à l'intérieur de la classe par MyClass.myMethod.
Méthode de classe :
  • une méthode de classe ne prend pas d'instance, mais prend la classe comme premier argument :
    def MyClass:
        @classmethod
        def myMethod(myClass, arg1, arg2):
            ...
        
  • on l'utilise à l'extérieur et à l'intérieur de la classe par MyClass.myMethod.
Méthode de classe pour construire un objet (quand constructeurs très divers nécessaires) :
@classmethod
def buildObject(cls, arg1, arg2):
  self = MyClass.__new__(cls)
  self.arg1 = arg1
  self.arg2 = arg2
  
A appeler avec myObj = MyClass.buildObject(arg1, arg2)

Divers

Pour accéder à tous les champs d'un objet : vars(myObj), ou aussi myObj.__dict__
Méthodes et champs rajoutés à une classe ou un objet :
  • on peut définir des nouvelles méthodes pour une classe en dehors de celle-ci :
    • on définit d'abord la fonction :
      def myFunc(self):
      	print('ok')
            
    • puis, on l'associe à un nom de méthode : MyClass.myMethod = myFunc
  • quand on a un objet, et que l'on veut lui associer une propriété supplémentaire non déclarée dans l'objet, on peut faire : myObj.newProp = myValue (très pratique parfois pour une utilisation locale, même si bien sûr pas propre).
Méthode __new__ :
  • c'est une méthode de classe qui est celle qui construit réellement l'objet.
  • son premier argument doit être la classe, et elle doit renvoyer l'objet construit.
  • __init__ est appelé juste après avec l'objet construit pour faire les initialisations.
  • il est très rare d'avoir besoin de réimplémenter __new__, en général, on ne la définit pas.
Destructeur : __del__ : méthode qui est appelée quand l'instance va être détruite.
Classe singleton :
class Singleton(object):
    _instance = None

    def __new__(myClass):
        if Singleton._instance is None:
            Singleton._instance = object.__new__(myClass)

        return Singleton._instance

    def __init__(self):
        self._var = 7

    def getVar(self):
        return self._var

s1 = Singleton()
print(s1.getVar())
print(id(s1))
s2 = Singleton()
print(s2.getVar())
print(id(s2))
  
c'est le même id d'objet qui est récupéré avec les 2 constructions.
Représentation d'un objet sous forme de chaîne de caractère :
  • implémenter la méthode __str__ pour surcharger la représentation avec str.
  • implémenter la méthode __repr__ pour surcharger la représentation avec repr.
  • quand on fait un print d'un objet : si __str__ est défini, c'est cette méthode qui est utilisée, sinon, si __repr__ est définie, c'est elle qui est utilisée et sinon, c'est la représentation par défaut (du genre <__main__.MyClass instance at 0x7fbcaf00c9e0>
  • différence entre str et repr : repr est censé fournir une représentation unique de l'objet, alors que str est censé fournir une représentation lisible de l'objet.
  • il est recommandé de toujours implémenter __repr__ et de le faire éventuellement pour __str__
Appel au constructeur de la classe de base :
class A(object):
 def __init__(self):
   print("world")

class B(A):
 def __init__(self):
   print("hello")
   super(B, self).__init__()
  
Classe nested :
  • on peut definir une classe à l'intérieur d'une classe :
    class MyClass:
      ...
      class MyNestedClass:
        ...
        
  • pour appeler MyNestedClass, il faut l'appeler par MyClass.MyNestedClass, y compris à l'intérieur de MyClass !
Définition des opérateurs : une classe peut réimplémenter les opérateurs en implémentant certaines fonctions :
  • exemple pour l'addition : la méthode suivante est appelée self = obj1 avec obj = obj2 quand on fait : obj1 + obj2 :
    def __add__(self, obj):
      ...
        
  • __add__ pour +
  • __sub__ pour -
  • __mul__ pour *
  • __truediv__ pour /
  • __floordiv__ pour //
  • __pow__ pour **
  • __mod__ pour %
  • __and_ pour & (bitwise and)
  • __or_ pour | (bitwise or)
  • __xor_ pour ^ (bitwise xor)
  • __invert__ pour ~ (bitwise not)
  • __lshift__ pour <<
  • __rshift__ pour >>
  • et aussi pour les comparaisons : __lt__, __le__, __gt__, __ge__, __eq__ et __ne__
Exemple d'implémentation de l'égalité (sinon, c'est la comparaison des adresses mémoire qui est utilisée) :
class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age

  def __eq__(self, person):
    if not isinstance(person, Person):
      return False
    return self.name == person.name and self.age == person.age
    
p1 = Person('Jean', 43)
p2 = Person('Pierre', 36)
p3 = Person('Jean', 43)
print(p1 == p2); print(p1 == p3)
  
renvoie False, puis True (en l'absence de __eq__, renverrait False dans les 2 cas).
Classe qui retourne un callable :
  • class MyClass:
        def __init__(self, a):
            self.a = a
    
        def __call__(self, x):
            return x * self.a
        
  • quand on fait alors obj = MyClass(5), on obtient un callable que l'on peut utiliser comme une fonction : obj(2) donne alors 10.

Copyright python-simple.com
programmer en python, tutoriel python, graphes en python, Aymeric Duclert