Soucis Python — Les-mathematiques.net The most powerful custom community solution in the world

Soucis Python

Bonjour
J'essaie d'apprendre le Python, correctement cette fois-ci. Je commence par des choses extrêmement basiques. Je préviens les lecteurs en avance : j'apprends presque en autodidacte, mes codes vont être moches. Mon but c'est de comprendre.
J'ai créé un code pour afficher une division euclidienne. J'en ai 4 versions.
La première que j'ai écrite.
def DivEuc1(a,b):
q=a//b
r=a%b
t=print(a, 'modulo', b, '=', q, 'reste', r)
return t
Ensuite, j'ai écrit sous cette fonction :
print(DivEuc1(100,7))
Et je reçois comme réponse :

Ligne 1 : "100 modulo 7  = 14 reste 2"
Ligne 2 : "None"

Question 1 : d'où sort ce "None" ?

J'ai donc essayé autre chose. J'ai simplement enlevé le print dans la fonction.
def DivEuc2(a,b):
q=a//b
r=a%b
t=(a, 'modulo', b, '=', q, 'reste', r)
return t
Idem, j'ai écrit sous cette fonction :
print(DivEuc2(100,7))
Et je reçois comme réponse :

Ligne 1 : "(100, 'modulo', 7, '=', 14, 'reste', 2)"

Question 2 : le "None" a disparu, mais pourquoi maintenant le print me donne la chaîne de caractères "brute" encore entre parenthèses et avec les virgules/guillemets, contrairement au premier essai ?

J'ai ensuite fait comme ceci :
def DivEuc3(a,b):
q=a//b
r=a%b
t=print(a, 'modulo', b, '=', q, 'reste', r)
return t
Idem, j'ai écrit sous cette fonction :
DivEuc3(100,7)
Et je reçois enfin la réponse au format que je voulais :

Ligne 1 : "100 modulo 7  = 14 reste 2"

Question 3 : Je n'aime pas trop la présence du "print" à l'intérieur de la fonction. J'aimerais que le "return" de la fonction soit le couple $(q,r)$, ça je sais faire. Mais alors, il me faut un truc séparé pour print mon résultat. J'ai fait ceci :

def DivEuc(a,b):
q=a//b
r=a%b
t=[q,r]
return t

def DivEucP(a,b):
print(a, 'modulo', b, '=', DivEuc(a,b)[0], 'reste', DivEuc(a,b)[1])
return

print(DivEuc(100,7))
DivEucP(100,7)
Et je reçois comme réponse:

Ligne 1 : "[14,2]"
Ligne 2 : "100 modulo 7  = 14 reste 2"

Là, moi je suis content, ça fait ce que je voulais, ouf.
Question 4 : peut-on faire plus "propre" ou est-ce que c'est considéré comme du "bon code" sous cette dernière forme ?

Merci :) (et merci à AD pour m'avoir appris à afficher du code lisible sur le forum)

Réponses

  • Modifié (July 2023)
    1. C’est normal : la fonction print ne renvoie rien (None en Python), elle affiche des trucs.
    2. Là, tu affiches un tuple, pas seulement la suite de trucs contenus dans le tuple.
    3. La dernière ligne de ta fonction est inutile puisque tu ne te sers pas du résultat.
    4. Tu peux faire print(*DivEucP(100,7)), ça marchera tout aussi bien.
    Sinon, ta fonction DivEuc existe déjà, elle s’appelle divmod.
    Algebraic symbols are used when you do not know what you are talking about.
            -- Schnoebelen, Philippe
  • Modifié (July 2023)
    Je ne suis pas sûr de comprendre. Ma fonction DivEuc1 renvoie quoi, exactement ? Elle renvoie "$t$" mais $t$ c'est un Print(machin), donc $t$ c'est quel type d'objet en Python ?
    Si tu me dis que le "None" provient du fait que mon print n'a rien à printer (à confirmer pourquoi, d'où ma question sur ce qu'est $t$), la vraie question c'est d'où vient la première ligne dans cette version !  Si encore dans mon premier code, j'avais mis une ligne "DivEuc1(100,7)" je comprendrais d'où sort la Ligne 1 dans le résultat, mais tel quel, je ne comprends pas !?

    EDIT : je me doute bien que ce que j'essaie de faire existe déjà dans Python de base, mon but c'est d'apprendre à faire des choses et à comprendre pourquoi ça déconne quand ça déconne.
  • dpdp
    Modifié (July 2023)
    Tu vas voir, c’est tout bête comme un ordinateur. La fonction DivEuc1 renvoie la valeur stockée dans la variable t ; valeur qui correspond à ce que retourne la fonction print lorsqu’elle est appelée, soit… rien (ou plutôt, None). Mais attention, cela n’est pas la même chose que ne rien afficher du tout.
    Ainsi donc, le premier print, qui se trouve dans la fonction DivEuc1 et qui s’exécute à chaque appel de cette dernière,
    t=print(a, 'modulo', b,  '=', q, 'reste', r)
    affiche ce que tu veux (avant même de retourner la valeur None à t) tandis que le deuxième print, qui s’exécute (j’insiste) après la fonction DivEuc1 (tu réalises ici la composition de fonctions print$\circ$DivEuc1)
    print(DivEuc1(100,7))
    affiche finalement la valeur None qui est stockée dans t et est renvoyée par DivEuc1.
  • Modifié (July 2023)
    Je ne comprends pas tout à fait. Si comme code, je mets uniquement ceci :
    def DivEuc1(a,b):
    q=a//b
    r=a%b
    t=print(a, 'modulo', b, '=', q, 'reste', r)
    return t
    Donc sans la ligne :
    print(DivEuc1(100,7))
    Alors le code ne renvoie rien du tout, le retour est vide. Il n'y a aucune ligne "DivEuc1(100,7)" dans ce code, avec cette ligne je comprendrais que ça me renvoie la ligne "100 modulo 7 = 14 reste 2" mais là tel que je suis un peu perplexe.

    La seule ligne de mon code où je demande d'exécuter quelque chose est le "print(DivEuc1(100,7))", où je demande à mon code de print un truc. 
    Le truc que cette ligne doit afficher est le contenu du "return" de la fonction DivEuc1, c'est bien ça ? Auquel cas, c'est $t$. Sauf que $t$ n'est pas une valeur, ni une chaîne de caractères, c'est une exécution de la fonction "print". Donc là je comprends pourquoi ça affiche la ligne "None", mais je n'ai pas compris ce qui fait s'afficher le "100 modulo 7 = 14 reste 2" ici (puisque le code n'exécute jamais la fonction DivEuc1).

    EDIT : ou bien est-ce que la ligne "print(DivEuc1(100,7))" force l'exécution de la fonction DivEuc1(100,7) pour que le print sache quoi afficher ?
  • Ton code ne renvoie rien, car tu n’effectues aucun appel à DivEuc1. Si tu appelles DivEuc1 tu auras le résultat voulu, comme pour DivEuc3.
  • dpdp
    Modifié (July 2023)
    Enfin, print n'est pas un objet pyhton. print est simplement une fonction qui retourne objet d'un certain type (comme un int (10, -24, 979679869698…), une chaine de caractères ("", "a", "Bonjour",…), un float (-12, 3.14, 0.00003342, 97969868976.097982732…)…). print renvoie systématiquement None pour signifier l'absence d'objet (ou de valeur) retourné. Rien de plus.
  • J'ai rajouté un petit EDIT à mon dernier message qui j'espère lèvera mon incompréhension ici.
  • dpdp
    Modifié (July 2023)
    Oui, c'est la ligne
    print(DivEuc1(100,7))
    qui appelle la fonction DivEuc1 et "force" son exécution comme tu le dis.
  • Ah, c'est tout de suite plus clair ! Merci. Maintenant il faut que je comprenne les commentaires sur mes trois autres questions
    Mon fichier de code contient déjà plus de lignes de cours que de lignes de code :D
  • Si, print est un objet (puisque tout est objet en Python, sauf les mots clé).
    Tape dir(print) pour voir.
    Algebraic symbols are used when you do not know what you are talking about.
            -- Schnoebelen, Philippe
  • dpdp
    Modifié (July 2023)
    Oui, nan, mais tu sais très bien ce que je voulais dire par cette phrase.
  • Du coup, dans ma deuxième version : le "print" force l'exécution de la fonction, mais le "return" de cette fonction n'est plus un print, mais une chaîne de caractères, et donc ce code-là printe cette chaîne de caractères "brute".
    Et dans ma troisième version : le "print" est de retour dans ma fonction de division, mais là j'exécute simplement la fonction (qui contient déjà un print quand on l'exécute) au lieu de re-demander qu'on me printe quelque chose, d'où l'absence du "None" parce que je n'ai jamais demandé de print un truc qui n'était pas une valeur. Dans ce code-là, la fonction de division ne stocke jamais le quotient/reste, mais elle affiche elle-même le résultat (la ligne "return" ne servant du coup à rien dans ce code puisqu'on ne l'utilise jamais).

    Je crois que je comprends à peu près B)

    @nicolas.patrois "dir(print)" ça renvoie : ['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
    Qu'est-ce que c'est ?
  • C’est cela, oui. Tu vois que c’est tout bête comme un ordinateur. ;)
    Pour le reste, c’est quelque chose dont tu ne devrais pas te préoccuper pour le moment… c’est la raison pour laquelle j’ai fait ce raccourci avec la fonction print. Mais ici, c’est impossible de faire des raccourcis afin de faire preuve d'un minimum de pédagogie sans que quelqu’un vienne montrer qu’il en sait plus que les autres. :/ M'enfin. Qu'importe. :)
  • J'ai une autre question (oui désolé, ça va vite).
    J'ai testé la fonction divmod intégrée à Python. "divmod(100,7)" renvoie "(14,2)". Entre parenthèses. J'ai demandé "print(divmod(100,7)[0])" et ça m'a renvoyé 14. Normalement, le [0] renvoie le premier élément d'une liste, mais j'ai toujours vu les listes écrites entre crochets en Python. Donc un n-uplet entre parenthèses, est-ce que c'est un type d'objet différent d'une liste, mais certaines opérations sont communes aux deux ? Ou bien parenthèses/crochets c'est pareil en Python ?
  • dpdp
    Modifié (July 2023)
    Un tuple () est grossièrement une liste dont les éléments sont immutables (qui ne peuvent être modifiés) tandis qu'une liste [] est une liste dont les éléments sont mutables (qui peuvent être modifiés).
  • Ah ! D'accord, c'est important comme distinction ça. 
    D'ailleurs @dp en principe, tout est "tout bête" quand on a les bases qu'il faut pour le comprendre (moyennant suffisamment de pédagogie, en tout cas). Mon souci avec Python, c'est que je n'ai jamais suivi de cours d'algorithmique, et que le cours de Python que j'ai eu expliquait très peu de Python, donc je suis un peu à un stade où je sais coder quelques trucs, mais je suis complètement incapable de faire du troubleshooting quand mon code ne marche pas. Obtenir les bases pour progresser tout seul après, c'est à peu près mon objectif ici, pour l'instant mon niveau de connaissances est un giga bordel lacunaire et je compense tout ce que je trouve.
  • Bien sûr ! Attention quand je dis que c’est bête comme un ordinateur, je n’insinue pas que tu es bête ! Au contraire, je veux juste te faire comprendre qu’un ordinateur est tout ce qu’il y a de plus bête : il effectue des opérations dans l’ordre que tu lui donnes, rien de plus, rien de moins. Ainsi, si tu analyses calmement les opérations que tu lui donnes et que tu déroules sur une feuille ton petit programme, tu peux normalement, plus ou moins facilement trouver ce qui cloche ! En plus ce sont de bons entrainements. ^^
  • Ben je vais continuer à m'entraîner ! Merci @dp et @nicolas.patrois :)
    Je vais sûrement ouvrir d'autres fils de questions ici dans les temps qui viennent.
  • Modifié (July 2023)
    Ah tiens si, petite dernière.
    Ma fonction finale était donc ceci (j'ai remplacé les crochets par des parenthèses, exprès) :
    def DivEuc(a,b):
    q=a//b
    r=a%b
    t=(q,r)
    return t
    Sauf que bien sûr, on peut raccourcir ça en :
    def EucDiv(a,b):
    return (a//b,a%b)
    Est-ce que cette fonction DivEuc est "moins optimale" que cette fonction EucDiv ? En principe, elle fait "la même chose" (elle calcule le quotient, elle calcule le reste, et elle renvoie les deux) mais je soupçonne que d'un point de vue gestion de mémoire la première est techniquement plus gourmande en ressources puisqu'elle définit 3 variables en plus à stocker quelque part pendant un moment.
  • dpdp
    Modifié (July 2023)
    À moins d’être dans des domaines précis où la vitesse d’exécution et l’espace disponible sont critiques, il est inutile de te prendre la tête avec ça. Le gain de place se compte en quelques octets ici. Par contre, la deuxième fonction est plus propre et moins propice aux erreurs ; néanmoins note que plus ton programme grossi, plus tu veux utiliser des variables afin de nommer (correctement, pas juste des a, b, c, d, etc…) les valeurs que tu utilises.
  • Oui, je me disais que dans le cas ici présent la différence ça serait peanuts, mais je voulais quand même savoir comment ça marche sur le plan théorique. Et forcément, dans un gros code on va donner des vrais noms aux choses ne serait-ce que pour pouvoir s'y retrouver, je m'imagine bien ça.
  • dpdp
    Modifié (July 2023)
    Je précise parce que les matheux ont trop souvent tendance à utiliser des noms de variables à une ou deux lettres comme en mathématiques ; sauf qu’agir de la sorte en info fait que ça devient vite le bordel. ^^'
    D’ailleurs, voici un des morceaux de code correspondant à divmod en python
        def __divmod__(self, other):
            """divmod(self, other): The pair (self // other, self % other).
    
            Sometimes this can be computed faster than the pair of
            operations.
            """
            return (self // other, self % other)
    même si ça ne paie pas de mine pour deux petites lignes, les noms sont bien choisis et logiques : self correspond à l’objet de la classe Real sur lequel on effectue l’opération et other correspond à l’objet qui va être utilisé pour effectuer l’opération.
    (bien que des fois on fasse des entorses à la règle… et c'est tout de suite moins facile à suivre)
  • Modifié (July 2023)
    Quizz : comment coder la fonction divmod sachant que tu n'as ni le droit à la division classique /  ni à la division euclidienne // ni à l'opérateur %

    Tu pourras faire une boucle et utiliser des multiplications!
  • Modifié (July 2023)
    Je propose :
    def QuizDiv(a,b):
    i=1
    while b*(i+1)<=a:
    i=i+1
    return(i,a-b*i)
    J'imagine que cette version est beaucoup moins économe en ressources... à moins que les divisions internes à Python utilisent ça comme méthode. Ce qui serait un bon prétexte à me faire faire ça.
  • Pas exactement, il faut t'habituer à faire des tests sur plusieurs cas limites
    Que vaut Div(3,6) par exemple chez toi?
  • Rien. A la fac quand je faisais ce genre de choses, on me disait "c'est vous l'utilisateur de votre programme, vous savez ce qu'il ne faut pas lui demander" :D
    En même temps mon programme va donner une réponse pour des entrées non-entières.
  • Plus sérieusement : j'ai remplacé i=1 par i=0, ça marche pour (3,6).
    Cependant : si je teste ma fonction avec $b=0$, la boucle while est infinie. Du coup, comment je fais pour empêcher une boucle infinie juste au cas où ?
  • Fais un test pour traiter le cas $b=0$ à part.
  • Si j'ai la suspicion que ça va faire une boucle infinie, je ne vais certainement pas faire un test. Je tiens à mon PC :D
  • dpdp
    Modifié (July 2023)
    La bonne pratique serait de faire, au début de la fonction
    assert b!=0
    de sorte à faire crasher le programme si $b=0$, puis de gérer l'exception.
    Edit: Tu peux même t'amuser à mettre un message qu'écrira assert lors du crash
    assert b!=0, "b doit être différent de 0"
    
    […]
    AssertionError: b doit être différent de 0
  • Comment ça "gérer l'exception" ?
    Le seul truc qui me vient à l'idée ça serait : if $b=0$, renvoyer une erreur, else le programme que j'ai écrit. Mais je ne sais pas faire "renvoyer une erreur".
  • print("erreur de division par 0") ou un truc comme ça ?
  • Quelque chose comme ça
        try:
            assert b!=0
        except AssertionError:
            print("b doit être différent de 0")
            exit(1)
    
    et si tu veux être un bg tu peux même lancer une exception de division par zéro
        if not b!=0:
            raise ZeroDivisionError("integer division or modulo by zero")
    
    un peu comme ça.
  • Modifié (July 2023)
    Plus court
    assert b!=0, "b doit etre different de 0"

    Edit : oups déjà posté pardon

  • Modifié (July 2023)
    Et après ça vient chouiner quand je pinaille parce que la fonction print est un objet. :D
    Si on veut gérer le cas où b est nul, on peut utiliser assert mais aussi ça :
    if b==0:
      raise ZeroDivisionError("On ne divise pas par zéro !")
    Ce qui permet de savoir exactement quel est le type de l’erreur.
    Algebraic symbols are used when you do not know what you are talking about.
            -- Schnoebelen, Philippe
  • En d'autres mots, si tu as une fonction QuizDiv par exemple, 
    et dans ta fonction principale, tu appelles QuizDiv(), puis tu fais différentes autres choses,
    - si dans QuizDiv, tu testes if b !=0 , tu peux gérer le cas spécifique b==0 et la vie continue.
    - si dans QuizDiv, tu testes le cas b !=0 avec des outils comme assert ou raise exception, dans le cas où b==0, la vie ne continue pas, tout ton programme s'arrête.


    Tu me dis, j'oublie. Tu m'enseignes, je me souviens. Tu m'impliques, j'apprends. Benjamin Franklin
  • Ici, comme c'est simple (il y a une seule exception, le cas $b=0$) je pense qu'il vaut mieux détailler un if/else. L'artillerie qui arrête tout le programme, je suis sûr que c'est utile dans d'autres situations, mais ici je n'en ai clairement pas besoin.
  • Ton programme s’arrête sauf si tu as prévu le coup avant d’appeler ta fonction avec ce que t’a proposé dp : le couple try et except.
    Algebraic symbols are used when you do not know what you are talking about.
            -- Schnoebelen, Philippe
  • Voici ce que ça pourrait (très rapidement) donner avec
    1. assert pour une erreur générique sans réelle information supplémentaire
      def QuizDiv(a,b):
          assert b!=0
          i=0
          while b*(i+1)<=a:
              i+=1
          return (i, a-b*i)
      
      try:
          print(QuizDiv(3,0))
      except AssertionError:
          print("integer division or modulo by zero")
          exit(1)
      
      Le programme ne s'arrête alors pas brusquement mais laisse le message d’erreur « integer division or modulo by zero » avant de quitter en renvoyant 1 au shell pour signifier l'erreur (si tu tapes alors echo $? dans ton shell après l’exécution de ce programme, tu verras afficher 1).
    2. ZeroDivisionError
      def QuizDiv(a,b):
          if not b!=0: # ou if b==0
              raise ZeroDivisionError
          i=0
          while b*(i+1)<=a:
              i+=1
          return (i, a-b*i)
      
      try:
          print(QuizDiv(3,0))
      except ZeroDivisionError:
          print("integer division or modulo by zero")
          exit(1)
      
      Cette fois-ci, bien que pour l'utilisateur final il se passe la même chose, du côté de celui qui programme (donc toi), on est dans un cas de figure bien différent. En effet, ici tu signifies bien que ton programme retourne une erreur de division par zéro (ce qui est bien entendu impossible). Ça peut paraitre être tatillon comme @nicolas.patrois avec sa fonction print qui est un objet mais en réalité c'est bien plus subtil et surtout utile pour le programmeur qui peut alors connaitre précisément le problème.
      En particulier, si lors de la phase de développement tu décides de n'écrire que
      def QuizDiv(a,b):
          if not b!=0: # ou if b==0
              raise ZeroDivisionError
          i=0
          while b*(i+1)<=a:
              i+=1
          return (i, a-b*i)
      
      print(QuizDiv(3,0))
      
      alors tu obtiens le détail de ce qu'il se passe et pourquoi ça déconne
      Traceback (most recent call last):
        File "/Users/dp/main.py", line 13, in <module>
          print(QuizDiv(3,0))
                ^^^^^^^^^^^^
        File "/Users/dp/main.py", line 7, in QuizDiv
          raise ZeroDivisionError
      ZeroDivisionError
  • Je ne savais pas trop comment intégrer tous les "opérateurs/fonctions" (je ne sais pas quel est le terme technique) try, except, raise une erreur, là au moins je vois comment les utiliser. Du coup, au lieu de dire au programme "exécute ça", tu lui dis "essaie d'exécuter" tant qu'il n'y a rien qui plante. Malin !
    Question subsidiaire, est-ce qu'il y aune méthode "universelle" pour juste forcer l'arrêt d'un programme s'il y a une boucle infinie dedans ? Je pense principalement à un while mal anticipé... quelque chose pour mettre un hard cap à l'exécution d'un programme (je ne sais pas, 10 minutes d'exécution, X quantité de mémoire, X itérations du while...). Un truc que je pourrais mettre dans chaque boucle while si je ne suis pas certain que j'ai bien évité les cas de boucle infinie.
  • Tu peux juste faire Ctrl-C pour tuer le processus. Si en plus tu veux gérer le cas et afficher un message, tu peux rester sur try/except, voici un exemple
    import time
    
    def func():
        while True:
            time.sleep(10)
    
    try:
        func()
    except KeyboardInterrupt:
        print("Process Killed")
    
  • On n’est pas obligé de tuer le processus avec exit(1) quand on lance une exception, tout dépend de ce qu’on veut en faire.
    Algebraic symbols are used when you do not know what you are talking about.
            -- Schnoebelen, Philippe
  • dpdp
    Modifié (July 2023)
    Si jamais tu veux faire un tour d'horizon de la programmation en python (et un peu plus encore), tu peux aller sur mesmanuels pour voir les manuels de NSI de 1ère et Tle aux éditions Hachette qui semblent encore accessibles librement : ici et . Les programmes de NSI étant complétement abusés, tu verras globalement tout niveau bases.
  • J'ai bien indiqué que je voulais signifier l'erreur dans le shell avec le code d'erreur 1, d'où la nécessité, ici, d'exit(1).
  • A une époque, on disait que la référence pour apprendre Python était le Swinnen ; on le trouve ici par exemple.
    Tu me dis, j'oublie. Tu m'enseignes, je me souviens. Tu m'impliques, j'apprends. Benjamin Franklin
  • Merci pour les références :)
Connectez-vous ou Inscrivez-vous pour répondre.
Success message!