Redéfinir un opérateur

Résolu/Fermé
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 - 1 nov. 2019 à 14:46
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 - 3 nov. 2019 à 15:33
Bonjour !

Le titre n'est pas très explicite, je vais donc davantage développer :

J'ai une application de maths en lignes de commande où l'utilisateur peut entrer des formules sur l'interpréteur, dont les opérations de base.
Il a la possibilité de changer la précision des résultats, les convertir...
Il y a donc toute une série de traitements à effectuer sur les résultats et quand l'utilisateur fait une opération de base, mon programme ne peut pas les faire car ce n'est pas moi qui ai conçu les opérateurs pour les nombres int et float.

Comment faire pour quand même appliquer ces traitements aux résultats ?
Merci d'avance !

6 réponses

Bonjour,

Toutes les opérations passent par les méthodes spéciales de python, par ex. quand on fait 1 + 2, cela passe par la méthode int.__add__

https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types

On ne peut heureusement pas redéfinir les méthodes des types de bases python, sinon bonjour les bugs que cela pourrait engendrer.

>>> int.__add__ = lambda s, v: s + v
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'int'


La seule solution est donc de définir ton propre type.
import math

class Int:
    def __init__(self, v):
        self.v = v

    def __mul__(self, v):
        print('on me demande de multiplier')
        return round(self.v * v, 3)

sqrt = lambda n: round(math.sqrt(n), 3)

print(Int(5) * sqrt(2))


C'est plus contraignant que de saisir un simple nombre, mais au moins on a le choix.
1
yg_be Messages postés 22723 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 25 avril 2024 1 476
2 nov. 2019 à 14:23
est-ce adéquat d'appliquer l'arrondi à chaque opération, n'est ce pas préférable d'arrondir uniquement avant l'affichage final?
0
khrug > yg_be Messages postés 22723 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 25 avril 2024
2 nov. 2019 à 14:46
C'était pour moi je pense.

En effet, pas bête, peut-être en contrôlant ce qui est donné en entrée ou fait en sortie avec sys.stdin et sys.stdout, mais je ne sais pas si c'est possible de le faire dans l'interpréteur python, il faudrait tester ^^
0
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
2 nov. 2019 à 14:31
Dans le code de Khrug ou le mien ?
Il me semblait que j'avais bien évité ce genre de choses, pourtant...
0
yg_be Messages postés 22723 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 25 avril 2024 1 476 > MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021
2 nov. 2019 à 15:46
je vois que tu fais trans() dans beaucoup de fonctions, j'imagine qu'elles peuvent être combinées, et donc que tu fais appel à trans() sur des résultats intermédiaires.
par exemple sqrt(sqrt(2)), pourquoi arrondir deux fois?
quand tu fais sqrt(2)*1000, à quel moment veux-tu arrondir?
0
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
2 nov. 2019 à 15:50
Justement : trouver une fonction universelle pour éxécuter une commande permetterait de ne pas faire trop d'appels à trans (), car si cette fonction renvoie quelque chose, il n'y aura qu'un seul traitement à faire !
0
yg_be Messages postés 22723 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 25 avril 2024 1 476
1 nov. 2019 à 16:46
bonjour, tu as donc une application, un utilisateur, un interpréteur, une série de traitements, des résultats, et un programme.
quel est le lien entre tout cela?
quel composant essaies-tu de créer ou de modifier?
0
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
2 nov. 2019 à 11:11
Désolé pour le retard.
En gros, mon appli charge des fonctions mathématiques et des classes dans l'interpréteur Python que l'utilisateur peut utiliser :

>>> prime (97) # L'utilisateur veut savoir si 97 est premier.
True
>>> _ # L'utilisateur est invité à taper une autre commande.

Si celui-ci tape :

>>> sqrt (2) # Pas besoin de davantage expliciter :-D
1.414

On voit ici que la précision à été limitée à trois décimales car j'ai écrit cette fonction, la valeur de retour est donc traitée.
Autre exemple :

>>> 5 * sqrt (2)
7.069999999999999

Hum !...
Vous avez très sûrement cerné le problème ; - )
Je souhaite donc pouvoir traiter aussi les résultats avec les opérations de base
En espérant avoir été plus clair cette fois !
0
yg_be Messages postés 22723 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 25 avril 2024 1 476
2 nov. 2019 à 11:42
peux-tu partager le code de ton appli, et donner un exemple de formule que tu veux également pouvoir traiter?
0
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
Modifié le 2 nov. 2019 à 14:12
Ce code est découpé en deux grandes parties : une pour les fonctions de base (sqrt, prime ou encore les fonctions trigonométriques), une pour tous les modules que l'utilisateur a le choix de charger au démarrage (Des classes pour manipuler des objets comme des vecteurs, des boules ou des rectangles).

Il y en a une dernière pour charger un certain nombre de constantes mathématiques comme pi, e ou encore la masse du Soleil, d'un proton...

Pour obtenir un retour formaté (pour éviter les résultats du genre -0.000000000000001 ou (3j - 0), quoi), j'ai fait une fonction qui traite le nombre et le renvoie bien propre et arrondi au nombre de décimales qu'on lui a demandé.
C'est la fonction trans (x) qu'on retrouve souvent.
Je ne vais pas mettre son code ici, ce serait inutile, en plus, il est moche : - )

Voilà un aperçu de mon code (pas trop non plus, il fait à peu près 4000 lignes :-D):
Au passage, désolé, je ne sais pas comment mettre un spoiler...

Partie constantes :


pi = 3.141592653589793238462

e = 2.71828182845904523536

phi = 1.618033988749894848204

G = 6.6743015e-11

i = 1j
j = 1j

c = 299792458

E = 1.602176634e-19

# Plus loin...
# Je ne mets qu'une seule classe, il en a trois, mais elles fonctionnent de la même façon

class MeasuresConstants:  # In meters
    def __init__ (self):
        self._inch = 0.0254   # Le programme stocke les valeurs exactes et les renvoie formatées à la demande avec des property
        self._foot = 0.3048
        self._yard = 0.9144
        self._nauticalMile = 1852
        self._league = 4828.032
        self._ua = 149597870000
        self._ly = 9460730472580800
        self._parsec = 30856775670528308


    def _getInch (self):
        return trans (self._inch)  # Valeur de retour formatée

    def _getFoot (self):
        return trans (self._foot)

    def _getYard (self):
        return trans (self._yard)
    
    def _getNauticalMile (self):
        return trans (self._nauticalMile)
    
    def _getLeague (self):
        return trans (self._league)

    def _getUa (self):
        return trans (self._ua)

    def _getLy (self):
        return trans (self._ly)

    def _getParsec (self):
        return trans (self._parsec)


    def _setSomething (self, nValue):  # On ne peut rien changer ici, on se fait donc gronder si on tente de modifier quoi que ce soit !
        print ("\n You aren't allowed to change a constant value !\n")


    inch = property (_getInch, _setSomething)
    foot = property (_getFoot, _setSomething)
    yard = property (_getYard, _setSomething)
    nauticalMile = property (_getNauticalMile, _setSomething)
    league = property (_getLeague, _setSomething)
    ua = property (_getUa, _setSomething)
    ly = property (_getLy, _setSomething)
    parsec = property (_getParsec, _setSomething)


m = MeasuresConstants ();  # L'utilisateur y accède par m.nom_de_la_constante



La partie fonctions :


def frequence (duration, t = 1):  # Renvoie une fréquence à partir d'une période et du nombre de répétitions dans cette période
    return trans (1 / (duration / t))


def sRadius (x):  # Renvoie le rayon de Schwarzschild d'un trou noir en mètres
    return trans ((2 * 6.67408e-11 * x) / 299792458**2)


def average (*params):  # Revoie la moyenne de ses arguments
    values = list (params)

    summ = 0
    i = len (values) - 1
    
    while i > -1:
        summ += values[i]
        i -= 1

    return trans (summ / len (values))



Dernière partie sur les objets (Je mets uniquement le cube) :
Je simplifie aussi en retirant les opérateurs pour les homothéties.
De plus, la fonction isOK (x) sert simplement à savoir si x est un réel positif différent de zéro (pour éviter que les petits malins ne s'amusent à mettre une arête négative au cube : - |)
La fonction trans (x) à été simplifiée et spécialisée en formated (x)


class Cube:
    def __init__ (self, edge = 1):  # Quand on créé le cube
        if isOK (edge):
            self._edge = edge

        else:
            print ("\n Invalid edge : it must be a positive non-zero real number !\n Initialization of default values...\n Done\n")
            self._edge = 1


    def _getEdge (self):
        return formated (self._edge)  # Retour formaté

    def _getVolume (self):
        return formated (self._edge**3)

    def _getSurface (self):
        return formated (self._edge**2 * 6)

    def _getDiagonale (self):
        return formated (self._edge * 1.732050807568878)


    def _setEdge (self, nEdge):  # Quand on veut changer l'arête du cube
        if isOK (nEdge):
            self._edge = nEdge

        else:
            print ("\n Invalid new edge : it must be a positive non-zero real number !\n")

    def _setVolume (self, nVolume):
        if isOK (nVolume):
            self._edge = nVolume**(1 / 3)

        else:
            print ("\n Invalid new volume : it must be a positive non-zero real number !\n")

    def _setSurface (self, nSurface):
        if isOK (nSurface):
            self._edge = (nSurface / 6)**0.5

        else:
            print ("\n Invalid new surface : it must be a positive non-zero real number !\n")

    def _setDiagonale (self, nDiadonale):
        if isOK (nDiagonale):
            self._edge = nDiadonale / 1.732050807568878

        else:
            print ("\n Invalid new diagonale : it must be a positive non-zero real number !\n")


    def __getattr__ (self, name):  # Si l'utilisateur essaie d'accéder à un attribut qui n'existe pas
        print ("\n I'm so sorry, but there's no attribute named \"{}\" in this object !\n".format (name))

    def __repr__ (self):  # Lance une représentation graphique du cube
        print ("\n Loading resources...")
        system ('Graphics.pyw cube {0} {1} {2} {3}'.format (self.edge, self.volume, self.surface, self.diagonale))

        return "\n Done !\n"


    edge = property (_getEdge, _setEdge)
    volume = property (_getVolume, _setVolume)
    surface = property (_getSurface, _setSurface)
    diagonale = property (_getDiagonale, _setDiagonale)



Vous voyez donc pourquoi le traitement des résultats des opérations de base me pose problème.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
2 nov. 2019 à 14:21
Merci pour votre réponse !
En effet, votre méthode paraît tout à fait logique, mais comment faire pour éviter au pauvre utilisateur de se coltiner l'écriture :

>>> Int (5) + Float (6.9)

Merci !
0
De toute façon si l'utilisateur est dans un interpréteur python, il aura accès à tout, donc à round, alors peut-être qu'il serait mieux de faire une petite interface graphique, et lors du calcul du résultat, l'arrondir à 3 chiffres avant de l'afficher. Ce qui serait quand même plus simple.
0
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
Modifié le 2 nov. 2019 à 15:10
Justement, j'aimerais faire cela MAIS :

Pour faire une interface, ça va.

Par contre, pour interpréter les commandes de l'utilisateur, c'est un foutoir incroyable :
il y a deux fonctions pour éxécuter une commande Python : exec (cmd) et eval (cmd).
Il faut faire un mélange fumeux des deux car eval () fonctionnera bien pour les opérations,
exec () pour la création des objets...

Bref, entre les exeptions et les bugs incompréhensibles, je ne m'y retrouve pas.

Peut-être existe-il une fonction plus universelle pour cela ?
0
khrug > MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021
2 nov. 2019 à 15:47
Je ne comprends pas de quoi tu parles pour la création des objets, objets de quoi ?
0
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
2 nov. 2019 à 16:58
Dans l'appli, on peut créer des instances de classes comme des cubes ou des cylindres pour accéder à leurs propriétés, les manipuler ou les modéliser :
0
khrug > MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021
3 nov. 2019 à 10:44
Bonjour,

Je ne connais pas nyanmaths (c'est un module python ? une app externe ?), mais ça ne change pas grand chose à l'histoire, soit tu laisses l'utilisateur en roue libre, soit tu implémentes tout un tas de fonctionnalités via une interface graphique et là c'est un sacré boulot qui va demander beaucoup de temps.
0
MemeTech Messages postés 88 Date d'inscription mercredi 14 août 2019 Statut Membre Dernière intervention 7 janvier 2021 1
3 nov. 2019 à 15:33
Bon, après un certain nombre de tests et de bidouillages, j'ai l'impression que tout est ok !
Je mets le sujet en résolu dans deux jours si tout va bien et si personne n'a d'idées en plus pour améliorer la chose.

Merci encore pour votre aide !
0