Partage
  • Partager sur Facebook
  • Partager sur Twitter

Erreur de syntaxe match-case Exercice P2C1

Sujet résolu
    2 mars 2024 à 16:18:01

    Bonjour,

    Dans l'exercice P2C1 du cours "Apprenez les bases du langage Python", j'ai semble t-il un problème de syntaxe avec la déclaration match-case.
    Je précise que je suis sur Python 3.12.2.

    Voici le snippet en question :

    match resultat:
      case operation == "+":
        resultat = nombre_1 + nombre_2
      case operation == "-":
        resultat = nombre_1 - nombre_2
      case operation == "*":
        resultat = nombre_1 * nombre_2
      case operation == "/":
        if nombre_2 != 0:
          resultat = nombre_1 / nombre_2
        else:
          print("Erreur : division par zéro = Impossible")
          exit()
      case _:
        print("Erreur : opérateur non reconnu")
        exit()

    Et voici le message d'erreur que le terminal me retourne :

    case operation == "+":
                       
    SyntaxError: invalid syntax

    Il semble que cela ait cassé Replit qui depuis tourne en boucle (coincé sur working) dès que je "Run".

    Je précise enfin qu'en me servant des déclarations if/elif/else, je n'ai aucun problème mais je ne trouve pas la solution pour y parvenir avec match-case.
    Je ne trouve rien non plus pour vérifier si la syntaxe, dans ce cas précis, est bonne ou non (il semblerait que non de toute manière).

    Si l'un de vous n'a serait-ce qu'un début de piste, n'hésitez pas !

    Merci !

    -
    Edité par Mad_Life 2 mars 2024 à 16:21:13

    • Partager sur Facebook
    • Partager sur Twitter
      2 mars 2024 à 18:41:06

      Sur tes case, tu dois mettre uniquement la chaîne que tu recherches, pas un test

          case '+':

      • Partager sur Facebook
      • Partager sur Twitter

      Le Tout est souvent plus grand que la somme de ses parties.

        2 mars 2024 à 20:52:28

        Et fait "match operation" du coup
        • Partager sur Facebook
        • Partager sur Twitter
          2 mars 2024 à 22:37:18

          En effet, une étourderie qui m'a prit du temps mais que j'ai fini par voir...

          Je vous remercie pour vos retours !

          • Partager sur Facebook
          • Partager sur Twitter
            3 mars 2024 à 1:45:26

            Une autre solution serait d'utiliser le dictionnaire d'opérations (les clés seraient les opérateurs et les valeurs, les fonctions lambdas avec l'opération à calculer), par exemple

            operations = {
                "+": lambda x, y: x + y,
                "-": lambda x, y: x - y,
                "*": lambda x, y: x * y,
                "/": lambda x, y: x / y if y != 0 else "Erreur : division par zéro = Impossible"
            }



            • Partager sur Facebook
            • Partager sur Twitter

            Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
            La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

              3 mars 2024 à 2:27:31

              @fred1599: j'ai déjà utilisé ce genre de truc avec les dictionnaires. J'aime bien.

              Mais je pense que le but de l'exercice était l'utilisation de match / case

              • Partager sur Facebook
              • Partager sur Twitter

              Le Tout est souvent plus grand que la somme de ses parties.

                3 mars 2024 à 2:39:37

                ok, ça lui permettra tout de même de voir une pratique équivalente en terme de fonctionnalité et meilleure en terme d'efficacité...

                Je n'ai jamais utilisé match case sans doute parce-que je n'en vois pas l'intérêt !

                -
                Edité par fred1599 3 mars 2024 à 2:48:17

                • Partager sur Facebook
                • Partager sur Twitter

                Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                  3 mars 2024 à 8:46:04

                  L'utilisation d'un dictionnaire + lambda à son charme (notamment quand le dictionnaire est destiné à évoluer en cours d'exécution).

                  Mais en terme d'efficacité, ça reste à prouver. Dans le cas de cet exemple, avec un match-case, au pire il va y avoir simplement une cascade de 4 comparaisons (en moyenne 2).  La recherche dans un dictionnaire,  ca passe par un appel au hachage, au moins une comparaison, et derrière il y aura l'application d'une lambda, ce qui n'est pas gratuit. Un petit benchmark pour avoir des chiffres qui nous éclaireraient ?

                  Pour ce qui est de l' expressivité du code, avec le match on voit tout de suite l'intention, tester 4 valeurs + traitement anomalie, qu'il faut ajouter à coté si on utilise un dictionnaire.

                  Ps : match-case a été introduite dans python 3.10. Il est donc normal que les gens qui utilisent python depuis longtemps déclarent qu'on peut trés bien s'en passer (par un hack dont ils sont fiers et coutumiers), c'est ce qu'ils ont fait jusque-là. D'autres y verront un truc très important qui est la solution à tous les problèmes, surtout que ça leur permet de montrer aux vieux un truc qu'ils ne connaissent pas

                  -
                  Edité par michelbillaud 3 mars 2024 à 8:58:07

                  • Partager sur Facebook
                  • Partager sur Twitter
                    3 mars 2024 à 10:10:36

                    Faut avouer que je m'attendais à mieux niveau efficacité, mais avec les dictionnaire on peut encore améliorer les temps...

                    La clarté, la maintenabilité et la flexibilité du code sont souvent des considérations plus importantes, surtout lorsque les différences de performance sont minimes. La méthode du dictionnaire offre une grande flexibilité et peut rendre le code plus modulaire et facile à étendre, ce qui peut être un avantage significatif dans de nombreux scénarios.

                    import timeit
                    
                    def calculatrice_match(nombre_1, nombre_2, operation):
                        match operation:
                            case "+":
                                return nombre_1 + nombre_2
                            case "-":
                                return nombre_1 - nombre_2
                            case "*":
                                return nombre_1 * nombre_2
                            case "/":
                                if nombre_2 != 0:
                                    return nombre_1 / nombre_2
                                else:
                                    print("Erreur : division par zéro = Impossible")
                                    exit()
                            case _:
                                print("Erreur : opérateur non reconnu")
                                exit()
                    
                    def calculatrice_if_else(nombre_1, nombre_2, operation):
                        if operation == "+":
                            return nombre_1 + nombre_2
                        elif operation == "-":
                            return nombre_1 - nombre_2
                        elif operation == "*":
                            return nombre_1 * nombre_2
                        elif operation == "/":
                            if nombre_2 != 0:
                                return nombre_1 / nombre_2
                            else:
                                print("Erreur : division par zéro = Impossible")
                                exit()
                        else:
                            print("Erreur : opérateur non reconnu")
                            exit()
                    
                    def calculatrice_dict(nombre_1, nombre_2, operation):
                        operations = {
                            "+": lambda x, y: x + y,
                            "-": lambda x, y: x - y,
                            "*": lambda x, y: x * y,
                            "/": lambda x, y: x / y if y != 0 else print("Erreur : division par zéro = Impossible")
                        }
                        func = operations.get(operation)
                        if func:
                            return func(nombre_1, nombre_2)
                        else:
                            print("Erreur : opérateur non reconnu")
                            exit()
                    
                    # Utilisation directe des opérateurs pour les opérations courantes
                    def add(x, y): return x + y
                    def sub(x, y): return x - y
                    def mul(x, y): return x * y
                    def div(x, y): return x / y if y != 0 else "Erreur : division par zéro = Impossible"
                    
                    def calculatrice_optimisee(nombre_1, nombre_2, operation):
                        func = operations.get(operation)
                        if func:
                            return func(nombre_1, nombre_2)
                        else:
                            print("Erreur : opérateur non reconnu")
                            exit()
                    
                    operations = {
                        "+": add,
                        "-": sub,
                        "*": mul,
                        "/": div
                    }
                    
                    
                    nombre_1 = 10
                    nombre_2 = 5
                    operation = "+"
                    
                    # Benchmark pour calculatrice_match
                    temps_match = timeit.timeit('calculatrice_match(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                    
                    # Benchmark pour calculatrice_if_else
                    temps_if_else = timeit.timeit('calculatrice_if_else(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                    
                    # Benchmark pour calculatrice_dict
                    temps_dict = timeit.timeit('calculatrice_dict(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                    
                    # Benchmark pour calculatrice_dict_optimise
                    temps_dict_optimise = timeit.timeit('calculatrice_optimisee(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                    
                    print(f"Temps d'exécution avec match: {temps_match} secondes")
                    print(f"Temps d'exécution avec if-else: {temps_if_else} secondes")
                    print(f"Temps d'exécution avec dictionnaire: {temps_dict} secondes")
                    print(f"Temps d'exécution avec dictionnaire optimise: {temps_dict_optimise} secondes")
                    

                    Les résultats

                    Temps d'exécution avec match: 0.07410755900491495 secondes
                    Temps d'exécution avec if-else: 0.06876738999562804 secondes
                    Temps d'exécution avec dictionnaire: 0.3240111149934819 secondes
                    Temps d'exécution avec dictionnaire optimise: 0.12330839000060223 secondes

                    On remarque que match et if-else sont équivalents et mieux que mon dictionnaire pour 1000000 d'itérations, qui n'est évidemment pas représentatif du nombre d'itérations dont on aura besoin pour ce type de problème.

                    J'ai ajouté la solution d'un dictionnaire optimisé qui se rapproche de très près des deux solutions (match et if-else).

                    J'ai fais aussi un autre test, avec pypy qui malheureusement ne supporte pas match (version 3.8 pypy), mais comme if-else est équivalent à match, on peut comparer match de manière indirecte.

                    Temps d'exécution avec if-else: 0.0036038779944647104 secondes
                    Temps d'exécution avec dictionnaire: 0.06391517700103577 secondes
                    Temps d'exécution avec dictionnaire optimise: 0.007861538993893191 secondes

                    Là on voit que la différence est moindre entre dictionnaire optimise et if-else.

                    L'accès à un élément dans un dictionnaire nécessite de calculer le hash de la clé et de vérifier l'égalité, ce qui peut être plus coûteux en termes de performance par rapport à une suite de tests if-elif ou à l'utilisation de match qui sont optimisés pour des comparaisons directes de valeurs.

                    • Partager sur Facebook
                    • Partager sur Twitter

                    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                      3 mars 2024 à 17:35:23

                      [Peux pas vous montrer le code et les résultats parce que le site est encore pété (*) pour firefox/linux sur pc, mais faut aussi voir ce que ça donne avec les 4 opérations. Edit: remarche.]

                       Pour if-then-else et match,  les opérations sont comparées l'une après l'autre donc ça prend plus de temps pour - que pour + qui vient en premier, plus pour *,  et encore plus pour /, à cause de la comparaison avec 0 qui s'y ajoute.

                      Bilan sans surprise : le temps ne varie guère avec les dictionnaires. Une collision sur 4 chaînes de longueur 1, ça serait quand même pas de bol, mais ça aurait pu arriver.

                      (*) ça va mieux après effacement de toutes les données en cache du navigateur. Une modif sur le site a dû l'empoisonner...

                      Du coup, comme dit l'autre, voila le bench

                      def bench():
                          print(f'# operation {operation}')
                          temps_match = timeit.timeit('calculatrice_match(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                          temps_if_else = timeit.timeit('calculatrice_if_else(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                          temps_dict = timeit.timeit('calculatrice_dict(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                          temps_dict_optimise = timeit.timeit('calculatrice_optimisee(nombre_1, nombre_2, operation)', globals=globals(), number=1000000)
                          print(f"avec match                : {temps_match:.3f} secondes")
                          print(f"avec if-else              : {temps_if_else:.3f} secondes")
                          print(f"avec dictionnaire         : {temps_dict:.3f} secondes")
                          print(f"avec dictionnaire optimise: {temps_dict_optimise:.3f} secondes")
                      
                      
                      nombre_1 = 5
                      nombre_2 = 10
                      
                      for operation in ["+", "-", "*", "/"]:
                          bench()
                      

                      Résultats

                      # operation +
                      avec match                : 0.093 secondes
                      avec if-else              : 0.077 secondes
                      avec dictionnaire         : 0.527 secondes
                      avec dictionnaire optimise: 0.144 secondes
                      # operation -
                      avec match                : 0.095 secondes
                      avec if-else              : 0.088 secondes
                      avec dictionnaire         : 0.520 secondes
                      avec dictionnaire optimise: 0.141 secondes
                      # operation *
                      avec match                : 0.107 secondes
                      avec if-else              : 0.104 secondes
                      avec dictionnaire         : 0.516 secondes
                      avec dictionnaire optimise: 0.141 secondes
                      # operation /
                      avec match                : 0.157 secondes
                      avec if-else              : 0.135 secondes
                      avec dictionnaire         : 0.538 secondes
                      avec dictionnaire optimise: 0.170 secondes
                      >>> 


                      si j'étais courageux, j'aurais généré un CSV et fait des courbes avec gnuplot...


                      -
                      Edité par michelbillaud 4 mars 2024 à 13:40:22

                      • Partager sur Facebook
                      • Partager sur Twitter
                        4 mars 2024 à 15:50:22

                        Donc on en déduis la même chose, merci pour la confirmation...
                        • Partager sur Facebook
                        • Partager sur Twitter

                        Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                        La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                          4 mars 2024 à 17:53:51

                          Ce qui me paraissait utile, c'est pas d'être en désaccord (et d'avoir raison, évidemment), c'est de chiffrer un peu pour ne pas rester sur du "on dit". Par exemple, on a maintenant des billes pour dire que l'application d'une lambda, ça coûte cher par rapport à l'utilisation d'une fonction définie (avec l'implémentation de l'interprète python qu'on a, précautions d'usage).

                          EDIT << en fait non (voir messages suivants). Ce qui coûte, c'est de construire une lambda chaque fois qu'on veut l'utiliser. Si on la met dans une variable qu'on réutilise, c'est pas pire que d'appeler une fonction. >>

                          Ma conclusion, c'est que match n'est pas  spécialement intéressant (point de vue rapidité d'exécution) pour faire un bête "switch", pour lequel un gros if elif elif elif fait le boulot. Par contre ça indique mieux l'intention du code.

                          OK, c'est l'utilisation basique qui est montrée en général pour le présenter, mais match est destiné à des usages beaucoup plus sérieux que faire un aiguillage. Le pattern matching avec destructuration d'un objet est beaucoup plus utile. voir la doc  https://docs.python.org/3/tutorial/controlflow.html

                          La sélection de cas par pattern matching, c'est pas jeune (fin des années 70 : Prolog, Miranda, programmation équationnelle etc) mais ça a fini par percoler dans les langages modernes, quasi un demi-siècle plus tard... Comme quoi faut pas désespérer.

                          -
                          Edité par michelbillaud 6 mars 2024 à 22:29:54

                          • Partager sur Facebook
                          • Partager sur Twitter
                            5 mars 2024 à 10:07:23

                            Merci à tous pour vos réponses, c'était pas évident de tout comprendre mais j'ai saisi le principal.

                            Il y a donc plusieurs façons de faire, certaines étant plus optimisées que d'autres mais en effet, dans l'exercice, il m'était demander d'utiliser match-case.

                            Très intéressant en tout cas les benchs. Faut encore que je vois ce qu'est un dictionnaire optimisé...

                            • Partager sur Facebook
                            • Partager sur Twitter
                              5 mars 2024 à 12:53:46

                              En fait, c'est pas le dictionnaire qui est optimisé, mais ce qu'on met dedans (associé à +, par exemple) et qui sera appelé ensuite

                              • soit une fonction définie par def
                              • soit une lambda

                              Edit: en fait c'est pas ça, voir seconde moitié du message.

                              pour essayer de voir, j'ai écrit plusieurs manières de faire, plus ou moins directes

                              import timeit
                              
                              def appel_direct(a, b):
                                  return a + b
                              
                              lambda_addition = lambda a, b : a + b
                              
                              fonction_addition = appel_direct
                              
                              def appel_lambda(a, b):
                                  return lambda_addition(a, b)
                              
                              def appel_fonction(a,b):
                                  return fonction_addition(a, b)
                              
                              def lambda_locale(a, b):
                                  f = lambda a, b : a + b
                                  return f(a, b)
                              
                              def tester(noms_fonctions):    
                                  for fun in noms_fonctions:
                                      temps = timeit.timeit(fun + "(123, 45)", globals = globals(), number = 10000000)
                                      print(f"{fun:20s}: {temps:.3f}") 
                              
                              
                              tester( ['appel_direct',  'lambda_addition', 'fonction_addition',
                                       'appel_fonction', 'appel_lambda', 'lambda_locale'] )
                              


                              Exécution

                              $ python3 Python-lambda.py 
                              appel_direct        : 0.552
                              lambda_addition     : 0.544
                              fonction_addition   : 0.544
                              appel_fonction      : 1.077
                              appel_lambda        : 1.078
                              lambda_locale       : 1.653
                              


                              Surprise, pas de différence notable entre l'appel d'une fonction ou d'une l'exécution directe. (3 premiers)

                              Les deux suivants montrent le délai induit par l'accès à une variable globale contenant la fonction ou la lambda.

                              La dernière (un peu hors sujet), c'est le temps de construction d'une lambda, si on le fait à chaque fois.

                              EDIT: pendant la sieste, il m'est venu à l'idée que la différence de performances dans le bench de @fred1599 venait précisément de là

                              • sa version "optimisée" (avec fonctions) utilise un dictionnaire construit à l'avance dans une variable globale
                              • par contre, et en revanche, la fonction calculatrice_dict construit un dictionnaire à chaque appel. Ce qui pénalise, forcément.

                               En mettant les deux sur un pied d'égalité

                              dict_lambda = {
                                  "+": lambda x, y: x + y,
                                  "-": lambda x, y: x - y,
                                  "*": lambda x, y: x * y,
                                  "/": lambda x, y: x / y if y != 0 else print("Erreur : division par zéro = Impossible")
                              }
                              
                              def calculatrice_dict(nombre_1, nombre_2, operation):
                                  func = dict_lambda.get(operation)
                                  if func:
                                      return func(nombre_1, nombre_2)
                                  else:
                                      print("Erreur : opérateur non reconnu")
                                      exit()
                               
                              # Utilisation directe des opérateurs pour les opérations courantes
                              def add(x, y): return x + y
                              def sub(x, y): return x - y
                              def mul(x, y): return x * y
                              def div(x, y): return x / y if y != 0 else "Erreur : division par zéro = Impossible"
                              
                              
                              
                              dict_functions = {
                                  "+": add,
                                  "-": sub,
                                  "*": mul,
                                  "/": div
                              }
                              
                              def calculatrice_optimisee(nombre_1, nombre_2, operation):
                                  func = dict_functions.get(operation)
                                  if func:
                                      return func(nombre_1, nombre_2)
                                  else:
                                      print("Erreur : opérateur non reconnu")
                                      exit()
                               

                              les temps d'exécutions c'est kif-kif bourricot

                              # operation +
                              avec match             : 0.113 secondes
                              avec if-else           : 0.076 secondes
                              avec dict de lambdas   : 0.147 secondes
                              avec dict de fonctions : 0.154 secondes
                              # operation -
                              avec match             : 0.096 secondes
                              avec if-else           : 0.091 secondes
                              avec dict de lambdas   : 0.145 secondes
                              avec dict de fonctions : 0.150 secondes
                              # operation *
                              avec match             : 0.114 secondes
                              avec if-else           : 0.108 secondes
                              avec dict de lambdas   : 0.144 secondes
                              avec dict de fonctions : 0.150 secondes
                              # operation /
                              avec match             : 0.164 secondes
                              avec if-else           : 0.140 secondes
                              avec dict de lambdas   : 0.171 secondes
                              avec dict de fonctions : 0.178 secondes
                              >>> 

                              Conclusion : on devrait faire la sieste plus souvent.  Ce qui optimise, c'est de constituer le dictionnaire une fois pour toute dans une variable globale. Pas à chaque appel. Qu'on y mette des lambda ou des fonctions, ca change pas grand chose.




                              -
                              Edité par michelbillaud 6 mars 2024 à 8:27:47

                              • Partager sur Facebook
                              • Partager sur Twitter
                                7 mars 2024 à 10:01:02

                                Une idée qui m'est venue au réveil : pour mieux voir la différence entre lambda et fonction (quand j'ai une idée quelque part...) j'ai écrit le petit code suivant

                                def my_function(a,b):
                                    return a + b
                                
                                my_lambda = lambda a, b : a + b
                                
                                # def test(name, fun, a, b):
                                #     r = fun(a,b) 
                                #     print(f'{name}({a},{b}) = {r}')
                                
                                # test("fonction", my_function, 2, 3)
                                # test("lambda  ", my_lambda  , 4, 5)

                                pour voir ce que python3 générait comme bytecode pour les deux, maintenant que je sais faire.

                                python3 -m dis python-lambda.py



                                On ne peut pas dire que ça soit très différent !

                                 0           0 RESUME                   0
                                
                                #
                                # fabrication de la fonction avec le code qui est plus loin, et rangement dans la variable my_function
                                  1           2 LOAD_CONST               0 (<code object my_function at 0x7f49b0365960, file "python-lambda.py", line 1>)
                                              4 MAKE_FUNCTION            0
                                              6 STORE_NAME               0 (my_function)
                                
                                #
                                # même chose pour la variable my_lambda
                                
                                  4           8 LOAD_CONST               1 (<code object <lambda> at 0x7f49b0365a30, file "python-lambda.py", line 4>)
                                             10 MAKE_FUNCTION            0
                                             12 STORE_NAME               1 (my_lambda)
                                
                                #
                                # l'évaluation du module retourne None je présume
                                             14 LOAD_CONST               2 (None)
                                             16 RETURN_VALUE
                                
                                # ----------------------------------------------------------
                                # le code de la fonction
                                
                                Disassembly of <code object my_function at 0x7f49b0365960, file "python-lambda.py", line 1>:
                                  1           0 RESUME                   0
                                
                                  2           2 LOAD_FAST                0 (a)
                                              4 LOAD_FAST                1 (b)
                                              6 BINARY_OP                0 (+)
                                             10 RETURN_VALUE
                                
                                # le code de la lambda
                                
                                Disassembly of <code object <lambda> at 0x7f49b0365a30, file "python-lambda.py", line 4>:
                                  4           0 RESUME                   0
                                              2 LOAD_FAST                0 (a)
                                              4 LOAD_FAST                1 (b)
                                              6 BINARY_OP                0 (+)
                                             10 RETURN_VALUE
                                

                                donc a priori aucune raison que l'un aille plus vite que l'autre.

                                -
                                Edité par michelbillaud 7 mars 2024 à 10:06:05

                                • Partager sur Facebook
                                • Partager sur Twitter

                                Erreur de syntaxe match-case Exercice P2C1

                                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                • Editeur
                                • Markdown