débordement de pile

a marqué ce sujet comme résolu.

Bonne chance !

Tu aurais dû donner quelques uns de ces messages d’erreurs, cela aide beaucoup à savoir d’où vient un problème.

+1 -0

Salut etherpin. Je tenais à te dire que finalement tes interventions me furent très utiles. Je reconnais que je n’avais pas apprécié ton conseil au début de mon post, conseil m’enjoignant de ne pas appeler moi-même la fonction mainloop(). Pas apprécié parce que j’aurais aimé que ce conseil soit assorti d’au moins une raison. (je l’ai dit : je débute en Python) Bref, j’ai reniflé autour des liens que tu m’as donnés et finalement compris (ou crû comprendre) certaines choses. Les raisons qui m’avaient incitées à appeler mainloop() sont assez mystérieuses parce que la vraie et bonne méthode pour sortir d’une fonction (return en l’occurrence) ne marchait pas pour une raison inconnue et que l’appel à mainloop(), lui fonctionnait. Les messages d’erreurs qui abondaient quand je quittais sauvagement le script (par un break ou en fermant la fenêtre de son exécution, étaient tous issus de l’interpréteur Python et pas de mon code, c’est pourquoi je ne les ai pas signalés ici. Peut-être vous auraient-ils étés utiles… je ne sais pas. En tout cas, j’ai fait un gros ménage dans mon code et depuis tout semble fonctionner correctement. J’ai rendu mon code exécutable en mode automatique et avant tes conseils, le script plantait après 45 minutes d' exécution. Maintenant, en mode automatique, il tournait ce matin à 7 heures alors que je l’avais lancé la veille au soir. Ha autre chose, encore. Je n’ai pas donné de détails sur mon script parce qu’il est inexploitable par quelqu’un qui n’aurait pas configuré son ordinateur de la bonne manière. En effet, j’utilise 2 fichiers binaires de petite taille (moins de 1 Ko chaqu’un) et ces fichiers sont écrits et mis à jours à chaque cycle du programme, soit environ deux fois par seconde en moyenne. A ce rythme là, aucun disque dur ne résisterait longtemps, qu’il soit mécanique ou pire SDRAM. J’ai donc créé un petit RAM-DISK (avec Linux, c’est facile) sur lequel je transfère à l’ouverture du script le contenu de ces fichiers qui sont sur mon disque dur. Et tant que le script tourne, les fichiers sont écrits et mis à jour dans le RAM disk. Ils réintégrent le DD quand j’arrête le script. (avec de temps en temps une petite sauvegarde en Disque dur, histoire de ne pas perdre tout ce qui a pu être écrit). Tu comprends donc que je ne pouvais pas vous imposer l’exécution d’un tel script. Quand le programme sera transféré sur un Raspberry,je me bidouillerai un montage électronique (c’est mon métier) de sauvegarde automatiquement en EEPROM pour me passer définitivement de disque dur. Voilà, merci encore.

Salut

Pour ce qui est des messages d’erreurs, je te les ai demandé à plusieurs reprises, car d’après la description que tu en faisais (ils mentionnaient un débordement de pile) ils donnaient la solution au problème.

Pour ce qui est du script, personne ne te l’a demandé pour l’exécuter : tout ce qu’on voulait, c’était le lire, en espérant repérer ce qui pouvait causer des soucis.

De manière générale, quand quelqu’un te demande quelque chose pour t’aider, c’est souvent pour une bonne raison. Alors, plutôt que d’ignorer la demande, tu pourrais soit y accéder, soit expliquer pourquoi tu refuses de le faire. Ça gagnerait du temps à tout le monde, et à toi en particulier : le sujet aurait été résolu lundi soir au plus tard.

J’espère que, pour la prochaine fois, tu sauras te souvenir de ce dernier point.


Ravi que ton problème soit résolu.

+1 -0

Jen suis heureux pour toi.
Si tu dois faire d’autres choses en Python, je te conseille de prendre un peu de temps pour te former.
Pour ma part, j’ai suivi deux MOOC sur le plate forme FUN :

Apprendre à coder avec Python
Convient bien à un débutant.

Python 3 : des fondamentaux aux concepts avancés du langage
très complet, je n’ai pas tout fait

+0 -0

Salut, tout le monde… J’ai encore des soucis, mais cette fois, je vous ai concocté un mini script schématisant (très grossièrement) mon script initial. Le but n’est pas de tenter d’en comprendre les tenants et les aboutissants mais simplement de se limiter à ce qui se passe dans ce script. Vous apercevrez au début du script principal l’instruction sys.setrecusionlimit(1000) la valeur 1000 est grossièrement celle qui préside à la destinée d’un MACOS, car en effet selon l' OS utilisé on a d’autres valeurs. Et cette valeur semble fixée par les auteurs du langage Python. Elle a pour but d' éviter le crash du système en cas de débordement ou de débauche de ressources… Vous pouvez après vous être rendu compte des effets de cette limitation porter la valeur à 10 000 soit 10 fois plus, Cela vous donnera le droit de faire durer l' essai un peu plus longtemps. Je vous laisse à vos réflexions quand à ce que je crois être un diktat qui nous semble imposé par le langage. Voici le listing de mon script. Il est sans danger pour vos systèmes, le RAM disk est supprimé et aucun accès disque dur n’est effectué. J’utilise PyCharm et Python 3

# !/usr/bin/python
from tkinter import *
from random import randrange
import time
#import sys
import os.path
import tkinter.font as tkFont
#from gui import *
# liste des widgets utilisés ici et normalement placés dans le module externe gui.py
font = tkFont
fen1 = Tk()
fen1.focus_force()
fen1.title("suspicious_Events")
fen1.geometry("900x800")
fen1.configure(bg="black")
label_port = Label(fen1, text=(0), fg="yellow", bg="black",
                     font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label_xxx = Label(fen1, text=(0), fg="yellow", bg="black",
                  font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label_port.place(x=700, y=40)
label_xxx.place(x=700, y=100)
label11 = Label(fen1, text="8870", fg="red", bg="black",
                font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label12 = Label(fen1, text="7881", fg="red", bg="black",
                font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label11.place(x=700, y=10)
label12.place(x=700, y=70)
label1 = Label(fen1, text="4000", fg="yellow", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label1.place(x=10, y=10)
label2 = Label(fen1, text="4120>>4156", fg="red", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label2.place(x=10, y=35)
label3 = Label(fen1, text="4157>>5958      ", fg="red", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label3.place(x=10, y=60)
label4 = Label(fen1, text="5959>>6010             ", fg="magenta", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label4.place(x=10, y=85)
label5 = Label(fen1, text="ZZZZ        ", fg="cyan", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label5.place(x=10, y=110)
label6 = Label(fen1, text="floor             ", fg="cyan", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label6.place(x=10, y=135)
label7 = Label(fen1, text="priv            ", fg="cyan", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label7.place(x=10, y=160)
label8 = Label(fen1, text="161.00   ", fg="cyan", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label8.place(x=10, y=185)
label9 = Label(fen1, text="is me ...           ", fg="cyan", bg="black",
               font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label9.place(x=10, y=210)
label10 = Label(fen1, text="spare         ", fg="cyan", bg="black",
                font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label10.place(x=10, y=235)
label1a = Label(fen1, text=(''), fg="yellow", bg="black",
              font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label1a.place(x=150, y=10)
label2a = Label(fen1, text=(''), fg="yellow", bg="black",
                 font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label2a.place(x=150, y=35)
label3a = Label(fen1, text=(''), fg="yellow", bg="black",
                 font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label3a.place(x=150, y=60)
label4a = Label(fen1, text=(''), fg="yellow", bg="black",
                 font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label4a.place(x=150, y=85)
label5a = Label(fen1, text=(''), fg="yellow", bg="black",
                 font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label5a.place(x=150, y=110)
label6a = Label(fen1, text=(''), fg="yellow", bg="black",
                 font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label6a.place(x=150, y=135)
label7a = Label(fen1, text=(''), fg="yellow", bg="black",
                font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label7a.place(x=150, y=160)
label8a = Label(fen1, text=(''), fg="yellow", bg="black",
                font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label8a.place(x=150, y=185)
label9a = Label(fen1, text=(''), fg="yellow", bg="black",
                font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label9a.place(x=150, y=210)
label10a = Label(fen1, text=(''), fg="yellow", bg="black",
                 font=tkFont.Font(family="Courier 10 Pitch", size=14, weight="bold"))
label10a.place(x=150, y=235)




# le script principal ...

sys.setrecursionlimit(1200)

def incremente_LC00():
    global ae
    ae = ae + 1
    if ae > 2000:
        ae = 1
    return ae
def affichage_events(ae):
    ae = ae +1
    print(ae)
    label_port.config(text=ae)
    label1a.config(text=ae * 1100)
    label2a.config(text=ae * 1000)
    label3a.config(text=ae * 900)
    label4a.config(text=ae * 800)
    label5a.config(text=ae * 700)
    label6a.config(text=ae * 600)
    label7a.config(text=ae * 500)
    label8a.config(text=ae * 3)
    label9a.config(text=ae * 2)
    label10a.config(text=ae * 1)
def quitter():
    print()
    print("#############################")
    print("# On est sorti du script... #")
    print("#############################")
    print()
    # print(stat)
    # print('moy ', moy)
    # print('moy souhaitée ', moy)
    # print('taux en cours ', taux_en_cours)
    #sys.exit(0)
    exit(0)
    quit()
def mon_filtre(event):
    lt = str(event.keysym)
    # if lt = 'KP_0'
    # if lt = 'KP_1'
    # if lt = escape
    if lt == 'KP_0' or lt == 'KP_1':
        return
    if lt=='t':
        incremente_LC00()
        affichage_events(ae)
    fen1.after_idle(mon_filtre(event))
ae=1
fen1.bind("<Key>", mon_filtre)
fen1.mainloop()

quitter()
exit(0)
quit()

Vous pouvez après vous être rendu compte des effets de cette limitation porter la valeur à 10 000 soit 10 fois plus, Cela vous donnera le droit de faire durer l' essai un peu plus longtemps. Je vous laisse à vos réflexions quand à ce que je crois être un diktat qui nous semble imposé par le langage.

PommeS2b

Non, c’est un diktat imposé par ton ordinateur, qui a une mémoire finie.

Quand tu appelles une fonction, un certain contexte est stocké dans la mémoire du programme (dans la pile pour être précis). Et quand cette fonction se termine, le contexte est retiré.

Si tu appelles une fonction dans ta fonction, alors les contextes s’empilent (d’où le nom). Et donc dans le cas d’une fonction récursive, la pile d’appels a tendance à bien grandir1.

La mémoire de l’ordinateur étant finie, la taille allouée à ton programme l’est aussi, et sa pile ne peut pas s’étendre à l’infini. Ainsi, ton système d’exploitation prévoit un mécanisme pour empêcher le programme d’allouer plus de mémoire sur cette pile, mais ça peut être un peu brutal.

Pour prévenir cela, Python fixe une limite inférieure en se disant que si tu la dépasses tu es est probablement dans un cas problématique (type récursion infinie) et qu’il te faut revoir ton code. Cela lui permet de lever une belle exception plutôt qu’un plantage du programme.

Et on le voit bien dans ton code : la fonction mon_filtre s’appelle elle-même à l’infini (ligne 139). Je pense que ce n’est pas comme ça que fonctionne after_idle qui doit fournir un mécanisme d’appel paresseux (sinon elle n’aurait pas d’intérêt), et donc prendre en arguments ta fonction et ses arguments.

Edit : donc effectivement ce serait plutôt un appel du type fen1.after_idle(mon_filtre, event) qui ferait que la fonction serait appelée par la boucle principale de tkinter plutôt que par mon_filtre, donc sans niveau de récursion.


  1. Hors optimisations sur les récursions terminales, mais ça n’existe pas en Python.
def mon_filtre(event):
    lt = str(event.keysym)
    # if lt = 'KP_0'
    # if lt = 'KP_1'
    # if lt = escape
    if lt == 'KP_0' or lt == 'KP_1':
        return
    if lt=='t':
        incremente_LC00()
        affichage_events(ae)
    fen1.after_idle(mon_filtre(event))

PommeS2b

Ton problème de récursion est certainement sur la ligne surlignée, en effet, elle exécute instantanément mon_filtre depuis mon_filtre sans condition (ou presque). Quel est l’objectif de cette ligne, que fait-elle?

Je vous laisse à vos réflexions quand à ce que je crois être un diktat qui nous semble imposé par le langage.

La limite de récursion est présente pour éviter que ton programme ne prenne toute la stack disponible. De plus, un algo récursif est souvent moins optimisé que sa variante itérative, surtout pour de grandes profondeurs, donc si un algo atteint cette limite de récursion (qui existe de toute façon, sous forme de compteur ou de taille de ram), c’est un signe qu’il est temps de le refactoriser en itératif.

PS: entwanne a été plus rapide

+1 -0

C’est vrai, j’utilise fen1.after_idle(mon_filtre(event)) j’aurais pu utiliser fen1.after(1000,mon_filtre(event))où 1000 est en millisecondes, mais utiliser idlle me semble préférable puisque je n’appelle la fonction que quand tkinter est en mode idle, c’est à dire quand il est disposé à me rendre la main. Cela étant comment faire concrètement pour qu’un script puisse se dérouler pendant un laps de temps très grand ? Sans utiliser les threads ou tout autres joyeusetés qui (je le sais d’expérience) ne seront pas acceptées dans le projet final qui doit s’ exécuter sur un Raspberry ? Avec idle je suis censé être tranquille car si tkinter me rend la main, c’est qu’il a effectué son garbage correctement. Pas vrai ? J’ai besoin de prélever les infos sur un flux de données circulant sur une paire de fils,je les charge dans un buffer que je vide après analyse. Ce processus ne doit théoriquement jamais s’ arrêter. Et paradoxalement surtout pas quand le trafic est intense sur mes fils. Bref, je suis mal, qui a un Cray à me vendre ? Dans la ligne que tu souligne je fais un callback qui semble inutiloe, mais il y a dans mon_filtre beaucoup d’appels à d’autres fonctions que je n’ai pas utilisées ici car inutiles. Ca plante très bien sans elles ….

+0 -0

Dans la ligne que tu souligne je fais un callback qui semble inutile

Il n’y a pas de callback dans ta ligne qui est strictement équivalent à

r = mon_filtre(event)
fen1.after_idle(r)

r n’est pas une callback et mon_filter ne se termine jamais.

Entwanne a indiqué comment faire.

Si je comprends bien l’appel récurrent a pour objectif la gestion du temps.
Voici cmment faire, d’après le tuto dont je t’ai donné le lien https://zestedesavoir.com/tutoriels/1729/programmation-avec-tkinter/.

import tkinter as tk

def incremente():
    "Incrémente le compteur à chaque seconde"
    global compteur
    compteur += 1
    compteur_lbl['text'] = str(compteur)

app = tk.Tk()
compteur = 0
compteur_lbl = tk.Label(app, text=str(compteur), font=("", 16))
compteur_lbl.grid(padx=8, pady=8)

app.after(1000, incremente)
app.mainloop()

Avec cette technique, ton code devient quelque chose du genre :

def mon_filtre(event):
    lt = str(event.keysym)
    # if lt = 'KP_0'
    # if lt = 'KP_1'
    # if lt = escape
    if lt == 'KP_0' or lt == 'KP_1':
        return
    if lt=='t':
        affichage_events(ae)

def incremente():
    "Incrémente le compteur à chaque seconde"
    global compteur
    compteur += 1
    compteur_lbl['text'] = str(compteur)
   
fen1.bind("<Key>", mon_filtre)
fen.after(1000, incremente)
fen1.mainloop()
+0 -0

Bref.Je déniche une pierre cachée dans le jardin des fervents de ce langage à la noix, pierre que tout le monde semble bien connaître mais dont personne ne m’avait parlé, faisant par là montre d’une pudeur que je comprends très bien. Tout cela me fait penser aux pieuses discutions que tenaient des anti-basics de jadis qui ne juraient que par la destruction du goto et que je ne tiens pas à réveiller. Je suis un peu agé,et c’est un euphémisme que de dire cela, et j’ai débuté l’informatique avec l’assembleur du 6502 lequel faisait largement appel aux JMP qui sont à la base de tout assembleur. Ces premiers assembleurs offraient même pour les plus évolués la possibilité de branchements à des labels ( bouh ! l’horreur diraient certains d’ici) et j’avais appris à faire des sauts dans toutes les directions car à l’époque les programmeurs savaient qu’avant de sauter quelque part il fallait se ménager un point de retour et géraient 'à la main' la pile de retour en tenant compte de tout et même des imprévisibles interruptions… Et là j’ai face à moi, les défendeurs d’un langage en passe de devenir l’un des plus populaires … quelle tristesse, on comprend mieux où va le monde et pourquoi. En fait la vérité est que le Basic n’est pas mort et qu’hélas ses nostalgiques non plus. Tout comme les Jacqueries ou les Ligues du Moyen-age étaient du Communisme avant l’heure et qu’elles renaîtront toujours et encore… Bon, promis je ferme le sujet et ne reviendrais plus sur ce langage. Ha tout de même, je réponds à ethepin : tout à fait tu as fait une fonction de ma GUI, et moi un module. Mais ton script modifié fonctionne comme le mien, : tu le lances, tu presses la touche t et après quelques secondes,çà plante. A noter que si tu donne une grande valeur à la variable d' environnement, (50 000 par exemple) ça plante encore mieux. Sans messages d' erreurs cette fois

+0 -4

Non mais encore une fois ce n’est pas propre à Python, ce n’est pas « une pierre carrée dans le jardin des fervents de ce langage à la noix » (au plaisir), c’est un comportement logique en raison du fait que la mémoire de ton ordinateur n’est pas illimitée.

Tu aurais exactement le même genre de soucis si tu exécutais le même code en C, en Ruby, en PHP, en Javascript, en OCaml, ou que sais-je encore. En tout cas tout langage avec des fonctions qui permet des appels récursifs, et donc d’atteindre la taille maximale de la pile d’appel prévue par le système.

Le comportement que tu observes quand tu augmentes la limite de récursion de Python à un très grand nombre, c’est que le programme se fait tuer par le système d’exploitation lui-même : il n’y a plus de mémoire disponible pour lui !

En assembleur tu verrais aussi quelque chose de similaire si tu remplissais continuellement la pile du programme. Ou alors tu observerais une boucle infinie si tu faisais continuellement des sauts au même endroit sans rien ajouter sur la pile.

Maintenant la réponse t’a été donnée avec une solution concrète, le code précis par lequel tu dois remplacer ta ligne défectueuse. Mais si tu choisis de l’ignorer, grand bien t’en fasse. :)

Ha tout de même, je réponds à ethepin : tout à fait tu as fait une fonction de ma GUI, et moi un module. Mais ton script modifié fonctionne comme le mien, : tu le lances, tu presses la touche t et après quelques secondes,çà plante. A noter que si tu donne une grande valeur à la variable d' environnement, (50 000 par exemple) ça plante encore mieux. Sans messages d' erreurs cette fois

PommeS2b

Dans le brouillon de script que j’ai proposé, la fonction ||monfiltre ||ne s’appelle pas elle -même. Il n’y a pas de débordement de pile de ce fait. Tu dis que ça plante, OK, mais avec quel massage d’erreur ?

P.S., j’ai moi aussi connu la programmation "spagetti" avec des Goto partout et des choses pires encore. J’ai commencé en assembleur IBM 360. Plus tard, j’ai programmé des systèmes "temps réel" Je dois dire que j’ai du mal à m’adapter à la programmation "objet". Par contre, je pense avoir compris la programmation par évènements comme on le fait avec Tinker. Mais, contrairement à toi, je pense que les évolutions actuelles permettent d’améliorer la qualité et la fiabilité des programmes.

+0 -0

Etherpin Bravo, ton code ne plante pas. C’est vrai.Et comment le pourrait-il ? Tu crées une fenêtre, tu y place un Label puis via la methode after tu lances une fonction qui après une seconde demande au Label se s’incrémenter de 1. Une seule fois pour toute. Alors pour que ça plante, faudrait vraiment y mettre du sien et se lever de bonne heure. Tu aurais écris une seule ligne de code telle

print("1")

qu’on aurait eu le même résultat Mais mes connaissances en Python sont trop neuves pour qu’un tel niveau d’abstraction me soit accessible.

Allez parce que je suis joueur, j’ai modifié le code d'@etherpin pour que l’incrémentation soit refaite en boucle… et magie, ça ne plante pas !

import tkinter as tk

def incremente():
    "Incrémente le compteur à chaque seconde"
    global compteur
    compteur += 1
    compteur_lbl['text'] = str(compteur)
    app.after(1000, incremente)

app = tk.Tk()
compteur = 0
compteur_lbl = tk.Label(app, text=str(compteur), font=("", 16))
compteur_lbl.grid(padx=8, pady=8)

app.after(1000, incremente)
app.mainloop()

Par contre on notera qu’avec after_idle plutôt que after, ça fonctionne moins bien. Tout simplement parce qu’à chaque tour de la boucle principale, le programme est occupé à appeler incremente plutôt que d’afficher la fenêtre.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte