Developpez.com - Rubrique Python

Le Club des Développeurs et IT Pro

Surcharge des opérateurs en Python : redéfinir dans une classe Python les opérateurs d'addition, de multiplication et de puissance pour les nombres complexes,

Un tutoriel de Denis Hulo

Le 2022-04-10 21:11:54, par User, Rédacteur/Modérateur
Bonjour,

Après avoir défini les opérations d'addition et de multiplication pour les nombres complexes, on explique comment redéfinir les opérateurs « + », « * » et « ** » dans une classe Python à l'aide de la surcharge d'opérateurs :

Surcharge des opérateurs en Python

Représentation algébrique d'un nombre complexe :



Enfin, pour compléter cette présentation, on explique comment ajouter à cette classe l'opérateur de comparaison « == » entre deux nombres complexes.

Bonne lecture à tous
  Discussion forum
11 commentaires
  • fred1599
    Expert éminent
    Hello,

    Je trouve le tutoriel très bien, et à mon sens, comme tout tutoriel, il est très rare qu'on y envoi tous les possibles...

    À mon sens, cela donne envie de continuer ce qui a été commencé, en y ajoutant des opérateurs et vérifier qu'on a bien compris ce que l'auteur a écrit.
    D'ailleurs c'est très bien de renvoyer vers une page donnant la liste des opérateurs existants.

    Mon petit doute, est juste un détail qui a peu d'importance... le fait que se soit du niveau "confirmé" serait en fait "confirmé" si on considère un débutant Python apprenant le langage, mais "débutant" si on considère un débutant en POO avec un niveau "confirmé" en Python.
    Étant donné que je considère cela comme de l'apprentissage POO et non un apprentissage Python, peut-être que je donnerai un point de précision sur l'objectif.

    Techniquement c'est suffisant et il n'y a pas grand chose à y ajouter à moins d'alourdir inutilement, et de démotiver le lecteur.

    C'est évidemment mon avis perso
  • MPython Alaplancha
    Membre expérimenté
    Bonjour,
    Envoyé par papajoker
    Mais dans mon cas, malheureusement cela complique la chose : je n'ai qu'un très très vague souvenir d'avoir "vu" les nombres complexes, il y à 40 ans. Donc baser une technique python sur une chose "complexe" pour moi ne va certainement pas aider puisque déjà rebuté dès le postula de départ.

    Un objet plus compréhensible serait pour moi par exemple une classe Color(r,g,b) (datetime c'est déjà fait...). Ici, c'est normalement plus parlant pour tous les développeurs et montre aussi que ce n'est pas uniquement réservé pour des mathématiques pures.
    J'imagine qu'aucun tutoriel ne pourrait faire l'unanimité.
    Moi non plus je ne suis pas trop math. D'ailleurs lors de mon apprentissage en python, il m'est arrivé esquiver des approches trop matheux, car je les trouvais peu ludiques et surtout je n'arrivais pas à voir en quoi cela pouvait me servir... puis vient le moment où j'ai besoin de certaine notion de math/géométrie pour réaliser un truc et là étonnamment ce que je trouvais austère l'est beaucoup moins

    En ce qui concerne les nombres complexes, ce ne sont pas non plus des notions trop avancées pour matheux. Ce tutoriel reste accessible au plus grand nombre
  • wiztricks
    Expert éminent sénior
    Envoyé par Hominidé
    En termes de sémantique, ne serait-il pas plus juste de parler de surcharge des méthodes spéciales utilisées par les opérateurs?
    La surcharge des opérateurs, c'est un "sucre" syntaxique permettant de remplacer "3 + 5" par un appel de fonction (ici operator.add(3, 5)).

    La surcharge de méthodes, c'est un polymorphisme ou plusieurs méthodes/fonctions avec le même nom mais acceptant des paramètres et des type de retour différents. Lorsqu'on appelle une de ces fonctions, le compilateur ou l'interpréteur va matcher la "bonne" en fonction du type de ses arguments.

    De fait, cela n'existe pas sur Python, mais on peut faire un équivalent à partir des (*args, **kwds) reçus par une fonction ou de functools.singledispatch.

    - W
  • Sve@r
    Expert éminent sénior
    Bonjour

    Les methodes __add__ (et autres) partent toutes du principe qu'on additionne (et autres opérations) deux complexes.
    Ce qui est vrai dans l'absolu car en mathématiques, tous les ensembles s'incluent les uns les autres. Les relatifs incluent les entiers en y rajoutant un sens de lecture, les décimaux incluent les relatifs en y rajoutant des parties de 10, les rationnels incluent les décimaux en y rajoutant des fractions autres que 10, les irrationnels incluent les rationnels en y rajoutant des nombres qui ne peuvent pas s'écrire sous forme de fraction, et les complexes incluent les irrationnels en y rajoutant une partie imaginaire.
    Ainsi si a=2+3i et b=5, alors a+b sont bien l'addition de deux complexes au sens strict du terme puisqu'un naturel fait partire de l'ensemble des complexes. Mais ton code, lui, ne le permet pas...

    Code python :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class cComplex: 
    	def __init__(self, reel=0, img=0): 
    		(self.reel, self.img)=(reel, img) 
    	# __init__() 
      
    	def __str__(self): 
    		return "%s, %si" % (self.reel, self.img) 
    	# __str__() 
      
    	def __add__(self, other): 
    		if isinstance(other, (int, float)): other=self.__class__(other, 0) 
    		elif isinstance(other, self.__class__): pass 
    		else: raise TypeError("on ne peut pas additionner un complexe avec un {}".format(type(other).__name__)) 
      
    		return self.__class__(self.reel + other.reel, self.img + other.img) 
    	# ___add__() 
      
    	def __radd__(self, n): return self+n 
    # cComplex() 
      
    a=cComplex(5, 2) 
    print(a) 
    print(a+1) 
    print(1+a+1) 
    print(a+cComplex(2, 2)) 
    print(a+"toto")

    Ensuite malheureusement c'est un peu dommage de faire un tutoriel pour un type déjà naturellement implémenté en Python
    Code python :
    1
    2
    3
    4
    5
    6
    a=5+2j 
    print(a) 
    print(a+1) 
    print(1+a+1) 
    print(a+2+2j) 
    print(a+"toto")
    Envoyé par papajoker 
    Puisque la sortie texte est du type "x + xi" , il pourrait être amusant d'avoir un "2 + 5i" + Complexe(7,2) ?

    C'est possible (en fait la base c'est convertir "2 + 5i" en complexe). Donc une méthode de classe qui décortique la string et retourne un complexe, du style
    Code python :
    1
    2
    3
    4
    5
    @classmethod 
    def fromString(cls, s): 
    	(reel, im)=...(extraction de s)... 
    	return cls(reel, img) 
    # fromString()
    Ensuite "2 + 5i + Complexe(7, 2)" s'écrira Complexe.fromString("2 + 5i") + Complexe(7, 2)...
  • MPython Alaplancha
    Membre expérimenté
    Bonjour,
    En termes de sémantique, ne serait-il pas plus juste de parler de surcharge des méthodes spéciales utilisées par les opérateurs?
    ...
    Merci du partage (j'avais vu le blog)
  • papajoker
    Expert confirmé
    bonjour,
    les nombres complexes sont trop complexes pour moi, donc je ne parlerais que de python.

    Dommage qu'il n'y ai pas de test du type dans __add__ ... __eq__

    Et puisque Complexe(5) est valide, on devrait pouvoir ajouter un entier à un complexe ? ou l'inverse ...

    Code :
    1
    2
    3
    4
    5
        def __radd__(self, other):
            if isinstance(other, int):
                return self.__add__(Complexe(other))
            else:
                raise TypeError("Type non supporté pour un Complexe")
    print(1 + Complexe(2,6))
    print(Complexe(2,6) +1)

    - Peut-être aussi surcharger __bool__() ?
    - Puisque la sortie texte est du type "x + xi" , il pourrait être amusant d'avoir un "2 + 5i" + Complexe(7,2) ?

    Si vous souhaitez avoir une liste plus complète des opérateurs, je vous invite à consulter cette page.
    En fait, dans ce lien/blog la liste est loin d'être complete par rapport à la documentation officielle (comme "+=" par exemple)
  • MPython Alaplancha
    Membre expérimenté
    Bonjour,
    Envoyé par wiztricks

    De fait, cela n'existe pas sur Python, mais on peut faire un équivalent à partir des (*args, **kwds) reçus par une fonction ou de functools.singledispatch.
    Merci du retour. Je ne mettais jamais penché sur functools.singledispatch...

    Envoyé par wiztricks

    La surcharge de méthodes, c'est un polymorphisme ou plusieurs méthodes/fonctions avec le même nom mais acceptant des paramètres et des type de retour différents. Lorsqu'on appelle une de ces fonctions, le compilateur ou l'interpréteur va matcher la "bonne" en fonction du type de ses arguments.
    Du coup lorsque l'on redéfinie une méthode lors d'un héritage de classe, il est impropre de parler de surcharge de méthode?
  • User
    Rédacteur/Modérateur
    Merci à vous 3 pour vos commentaires et précisions

    Je vais voir pour le lien vers la liste plus complète des opérateurs.

    Cordialement.
  • User
    Rédacteur/Modérateur
    Bonjour,

    Envoyé par Sve@r 
    ...
    Ce qui est vrai dans l'absolu car en mathématiques, tous les ensembles s'incluent les uns les autres. Les relatifs incluent les entiers en y rajoutant un sens de lecture, les décimaux incluent les relatifs en y rajoutant des parties de 10, les rationnels incluent les décimaux en y rajoutant des fractions autres que 10, les irrationnels incluent les rationnels en y rajoutant des nombres qui ne peuvent pas s'écrire sous forme de fraction, et les complexes incluent les irrationnels en y rajoutant une partie imaginaire.
    Ainsi si a=2+3i et b=5, alors a+b sont bien l'addition de deux complexes au sens strict du terme puisqu'un naturel fait partire de l'ensemble des complexes. Mais ton code, lui, ne le permet pas...

    Un nombre réel étant un nombre complexe dont la partie imaginaire est nulle, je suis resté sur la définition de la somme de 2 nombres complexes qui a pour partie réelle la somme des parties réelles de ces 2 nombres, et pour partie imaginaire la somme de leurs parties imaginaires.

    Je n'ai pas voulu gérer tous les cas de figures pour ne pas perdre le lecteur.

    D'autre part, tu sembles vouloir gérer également les réels au niveau de l'addition, dans ce cas pourquoi ne pas le faire aussi au niveau de l'affichage :

    Code :
    1
    2
    3
    a=cComplex(1.0,1.0) 
    b=cComplex(1.5,-1.0) 
    print(a+b)
    Ton code affiche :
    2.5, 0.0i

    Autant afficher simplement 2.5 dans ce cas.

    Ensuite malheureusement c'est un peu dommage de faire un tutoriel pour un type déjà naturellement implémenté en Python
    Code python :
    1
    2
    3
    4
    a=5+2j 
    print(a) 
    print(a+1) 
    ...

    En effet, beaucoup de choses sont déjà implémentées en Python, mais en fait je n'ai pas voulu créer une librairie de plus (cf. cmath).
    J'ai juste essayé de montrer simplement comment mettre en place ce type de surcharge, et il m'a semblé que les nombres complexes était un bon exemple pour le faire, quand bien même cela existe déjà en mieux

    Cordialement.
  • Sve@r
    Expert éminent sénior
    Envoyé par User
    Je n'ai pas voulu gérer tous les cas de figures pour ne pas perdre le lecteur.
    A mon avis c'était le contraire, gérer tous les cas de figure pour justement montrer au lecteur qu'il faut penser à plein de trucs (et s'il est perdu il prend son temps). J'ai écrit une classe "fractions" (en fait je suis parti de cet objet pour faire cet exemple) j'ai géré le cas "-x" mais aussi le cas "+x" (opérateur "__pos__" à réécrire sinon l'expression n'est pas reconnue), le cas "x-y", le cas "-y+x" (qui, même s'il donne la même chose, n'est pas la même opération), etc...

    Envoyé par User
    D'autre part, tu sembles vouloir gérer également les réels au niveau de l'addition, dans ce cas pourquoi ne pas le faire aussi au niveau de l'affichage
    L'affichage n'était pas le but de l'exemple. Je l'ai écrit minimaliste juste pour vérifier la validité des calculs.