IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PyQt Python Discussion :

Une console Python dans un QTextEdit ? [QtGui]


Sujet :

PyQt Python

  1. #1
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut Une console Python dans un QTextEdit ?
    Bonjour,

    Dans un de mes programmes, j'aimerais installer une console Python interactive dans le QTextEdit d'une fenêtre PyQt4. Un peu comme la partie interactive de Idle.

    J'ai cherché avec le module "code", mais je ne vois pas comment "brancher" les entrées et les sorties de l'interpréteur sur le QTextEdit.

    Quelqu'un a-t-il une piste?

    Merci d'avance!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  2. #2
    Expert éminent

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    Par défaut
    Salut,

    Dans Eric c'est implémenté avec QScintilla, je ne sais si l'exemple peut aider.

    Sous Linux, il est là : /us/lib/python3.3/site-packages/eric5/QScintilla/Shell.py

    Je suppose que ce doit être à peu près pareil pour eric4 ('site-packages' ou 'dist-packages' selon la version)

  3. #3
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    En fait, il y a plusieurs difficultés.
    La première étant qu'il faut s/classer InteractiveConsole pour redéfinir les méthodes:
    • runcode: il s'agit de récupérer les sorties qui vont dans .stdout, .stderr dans un buffer,
    • raw_input: pour qu'il lise où on veut.


    C'est ce que fait le code ci dessous.
    InteractiveConsole doit être s/classé pour définir les .read, .write, .quit qui vont bien.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    import sys
    import code
    import io
     
    class InteractiveConsole(code.InteractiveConsole):
     
        def runcode(self, code):
            '''redefined runcode to trap sys.stdout/stderr'''
            saved_fds = sys.stdout, sys.stderr
            sys.stderr = sys.stdout = sio =  io.StringIO()
            try:
                exec (code, self.locals)
            except SystemExit:
                self.quit()
            except:
                self.showtraceback()
            finally:
                data = sio.getvalue()
                sio.close()
                sys.stdout, sys.stderr = saved_fds
                self.write(data)
     
        def raw_input(self, prompt):
            try:
                line = self.read(prompt)
            except Exception as e:
                log.exception (e)
                raise SystemExit()
            else:
                return line
     
        def write(self, data):
            raise NotImplementedError("fatal: abstract method!!!")
        def read(self, prompt):
            raise NotImplementedError("fatal: abstract method!!!")
        def quit(self):
            raise NotImplementedError("fatal: abstract method!!!")
     
    def test_basics():
        '''Check InteractiveConsole hooks work as expected'''
        class Console(InteractiveConsole): 
            def write(self, data):
                print (data)
            def read(self, prompt):
                return input(prompt)
            def quit(self):
                sys.exit()
        console.interact()
     
    if __name__ == '__main__':
        test_basics()
    L'autre difficulté est de "pousser" l'exécution d'une instance d'InteractiveConsole dans un thread et de "coller" les .read, .write avec le GUI. Mais çà ne devrait pas vous poser de soucis particulier // et j'ai pas le temps de nettoyer mon code pour "sortir" ce qui a poussé "autour" mais qui ne sert à rien.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  4. #4
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Ça y est: j'ai réussi!

    Merci à tous les 2 pour les idées. Je suis parti du module "code" qui est fait pour ça, et j'ai fait l'adaptation à PyQt4: ça n'a rien de simple... J'ai dû d'ailleurs consulter une centaine de pages web, et je n'ai rien trouvé de concret sur le sujet.

    Les principes que j'ai utilisés dans le code ainsi que le code lui-même sont sur mon site: http://python.jpvweb.com/mesrecettes...console_python

    Je ne suis pas sûr que ce soit encore parfait: disons que c'est une version beta . En particulier, je n'ai pas réussi à arrêter l'exécution de l'interpréteur avec un ctrl-C à cause du thread: l'exception KeyboardInterrupt ne marche pas.

    Pour l'instant, ce programme répond à mon besoin initial, mais je reste à l'écoute de toute suggestion d'amélioration!

    Un exemple de ce que ça donne ci-dessous, l'objectif étant de pouvoir afficher la fenêtre dans n'importe quel programme PyQt, y compris traité par cx_freeze et s'exécutant sur un PC sans Python:

    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  5. #5
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,
    Bravo pour le temps passé à écrire le code.
    Corrigez quand même cette partie:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        def read(self, prompt):
            """lit la chaine à interpréter"""
            # envoi l'invite pour affichage
            self.write(prompt)
            # retourne la chaine dès qu'il y en a une de saisie
            while self.texte==None:
                pass
            texte = self.texte
            self.texte = None
            return texte
    Car grosso modo, tant que l'utilisateur n'aura pas tapé "<Return>", je ne vois pas trop comment cela pourrait faire autre chose que de bouffer le CPU pour rien.
    - W
    PS: Je n'ai pas encore eu le courage de coder cela ;-(
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  6. #6
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks,

    Je viens d'apporter 3 modifs:

    - pour tenir compte de ta remarque, j'ai remplacé dans la boucle le "pass" par "sleep(0.1)". Cela épargnera un peu le CPU. Je n'ai pas trouvé mieux que cette boucle d'attente jusqu'à ce qu'il y ait une instruction à exécuter. Il faudrait qu'on puisse mettre le thread en sommeil, et le réveiller par un simple message, mais je ne sais pas faire. Si tu as une meilleure idée que la boucle, n'hésite pas!

    - il a fallu que je mette un shake-hand pour l'affichage (entre interpy.write et visu.affiche), sinon, l'interpréteur envoyait les affichages trop vite, et ça plantait Python. Cela se fait avec un drapeau (okwrite), et j'ai simplement ajouté un verrou (mutexokwrite de type QtCore.QMutex) puisqu'il peut être modifié par le programme principal et par le thread.

    - la 3ème modif est intéressante puisqu'elle permet de fournir des listes d'instructions à exécuter dès le lancement de l'interpréteur. Cela permettra, par exemple, d'importer automatiquement des modules de fonctions supplémentaires adaptées au domaine traité: scientifique, financier, construction, etc... Cela fera une super calculatrice spécialisée (sans clavier pour l'instant).

    Des explications supplémentaires et le nouveau code sur mon site (http://python.jpvweb.com/mesrecettes...console_python).

    Il reste, à mon avis, à neutraliser les touches clavier qui ont une fonction dans le QTextEdit mais qui ne sont pas compatibles avec l'utilisation en console Python, ainsi qu'à trouver une solution à cette histoire de contrôle-C pour arrêter une exécution trop longue ou défaillante.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  7. #7
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Le protocole me semble affreusement compliqué. Mais je n'ai pas encore eu le temps de regarder tout çà.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Petit complément.

    J'étais parti de l'objectif "permettre à n'importe quel programme PyQt4 de lancer une console Python".

    J'ajoute une possibilité: pouvoir lancer la console Python à partir d'une icone située dans la zone de notification (System Tray). C'est très pratique de faire comme ça.

    Par exemple sous Windows:
    - au démarrage du PC, le programme se charge automatiquement et se lance, mais seule l'icone apparait dans la zone de notification.
    - Si on clique sur l'icone, la console Python s'affiche.
    - On l'utilise.
    - A un moment, on clique sur la petite croix pour fermer la fenêtre. En fait, elle ne se ferme pas: elle se cache (hide) et elle disparait de la barre des tâches.
    - En recliquant sur l'icone, elle s'affiche de nouveau (show) en ayant conservé sa configuration: on peut donc poursuivre une opération déjà commencée précédemment.

    Cela permet d'avoir une console Python disponible en permanence pour faire, par exemple, des calculs.

    J'ai ajouté un petit menu contextuel (clic droit) pour afficher la fenêtre "apropos" et pour quitter effectivement le programme, ce qui ferme la console et fait disparaitre l'icone de la zone de notification.

    On peut se débrouiller pour écrire le lanceur (sous-classement de QtGui.QSystemTrayIcon) afin de lancer le programme (ici la console Python) sans aucune modification de celui-ci.

    Si quelqu'un est intéressé, je peux donner des infos supplémentaires et du code.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  9. #9
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Et voilà comment on arrête un script trop long dans la console avec une exception "KeyboardInterrupt"!

    J'ai utilisé une solution que j'avais déjà exploité dans le passé: sys.settrace. Je sais que ce n'est pas fait pour ça mais plutôt pour du debugging, mais je n'ai rien trouvé d'autre (et personne sur le web non plus) et ça marche plutôt bien.

    Modif du code précédent:

    importation: sys est déjà importé.

    modification de la classe qui hérite de code.InteractiveConsole:

    - ajouter dans __init__:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.arretcalcul = False
    - modifier la méthode runcode comme suit:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        def runcode(self, code):
            """surcharge de runcode pour rediriger sys.stdout et sys.stderr"""
            # redirection de la sortie d'affichage
            self.std_sav = sys.stdout, sys.stderr
            sys.stdout = sys.stderr = sio = MonStringIO(self)
            # exécution de l'instruction Python compilée       
            try:
                sys.settrace(self.trace) # mise en place du traçage
                exec code in self.locals
                sys.settrace(None) # arrêt du traçage
            except SystemExit:
                sys.settrace(None) # arrêt du traçage
                self.quit()
            except:
                sys.settrace(None) # arrêt du traçage
                self.showtraceback()
            finally:
                # remettre la sortie d'affichage initiale
                sys.stdout, sys.stderr = self.std_sav
                sio.close()
    - ajouter les 2 méthodes:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        def trace(self, frame, event, arg):
            """méthode appelée à chaque ligne de code exécutée par exec"""
            if event == 'line':
                if self.arretcalcul:
                    self.arretcalcul = False
                    raise  KeyboardInterrupt ("Arret du calcul sur demande")
            return self.trace
     
        def stopexec(self):
            """ méthode appelée pour demander au thread de calcul de s'arrêter"""
            self.arretcalcul=True
    Il ne reste plus qu'à ajouter dans le traitement des touches clavier l'appel à la méthode précédente stopexec() pour la touche Ctrl-S (par exemple) et n'importe quel script Python en exécution s'arrête en donnant la trace de l'exception (ici KeyboardInterrupt).

    Attention, cela n'arrête que les scripts Python multilignes, puisque le principe de sys.settrace est justement d'interposer un arrêt possible (=l'appel à la méthode trace) entre chaque ligne Python: ça ne marcherait pas avec un code C ou tcl/tk.

    Ouf...
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  10. #10
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Le protocole me semble affreusement compliqué. Mais je n'ai pas encore eu le temps de regarder tout çà.
    A priori, les .write doivent aller au QTextEdit via l'event loop: pas besoin de synchronisation particulière.

    Pour les .raw_input, il est plus simple de remonter les "lines" via une queue.Queue: çà bloque l'interpréteur jusqu'à ce que l'event 'on_eol' lui
    remonte la ligne suivante.

    Côté code, çà simplifie quelque peu la logique.
    nota, je n'ai pas reporté la gestion de l'history pour ne garder que la logique de communication entre la qApp et l'interpréteur Python.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
     
    # use Qt4 threads
    # spot V2 and V3
     
    import logging
    ## if __name__ == '__main__':
    ##     logging.basicConfig(level=logging.DEBUG)
     
    log = logging.getLogger('sample')
     
    import sys
    PyVER = (sys.version_info.major, sys.version_info.minor)
    PyV3 = PyVER >= (3, 1)
    PyV2 = PyVER == (2, 7)
    assert not (PyV2 and PyV3), 'fatal: unsupported version %s' % '.'.join(int(x) for x in PyVER)
    del PyVER
     
    from PyQt4.QtCore import (QObject, pyqtSignal, QCoreApplication, QThread)
    import sys
    import code
    if PyV3:
        from io import StringIO 
        from queue import Queue
    elif PyV2:
        from io import BytesIO as StringIO 
        from Queue import Queue
     
    class Interpreter(code.InteractiveConsole, QObject):
     
        # don't recycle sio
        def runcode(self, code):
            """redirect outputs to StringIO"""
            std_sav = sys.stdout, sys.stderr
            sys.stdout = sys.stderr = sio = StringIO()
            sio.write = self.write
            try:
                exec (code, self.locals)
            except SystemExit:
                self.quit()
            except:
                self.showtraceback()
            finally:
                sio.close()
                sys.stdout, sys.stderr = std_sav
     
        on_write = pyqtSignal(str)
        on_raw_input = pyqtSignal(str)
        on_quit = pyqtSignal()
     
        def __init__(self, textEdit, locals=None, filename="<console>"):
            code.InteractiveConsole.__init__(self, locals, filename)
            QObject.__init__(self)
            self._inputQ = Queue(maxsize=1)
     
            # weaving
            self.on_write.connect(textEdit.write)
            self.on_raw_input.connect(textEdit.raw_input)
            self.on_quit.connect(textEdit.quit)
            textEdit._interpreter = self
     
        def interpret_line(self, line):
            log.debug('proxy.interpret_line, /%s/' % line)
            self._inputQ.put(line)
     
        def write(self, data):
            log.debug('proxy.write')
            self.on_write.emit(data)
     
        def raw_input(self, prompt):
            log.debug('proxy.raw_input')
            self.on_raw_input.emit(prompt)       
            data = self._inputQ.get()
            return data
     
        def quit(self):
            log.debug('proxy.quit')
            self.on_quit.emit()
     
        def interact(self, banner=None):
            me = QThread.currentThread()
            app = QCoreApplication.instance().thread()
            log.debug('interact on thread: %s' % me)
            log.debug('QApp on thread: %s' % app)
            assert me is not app
            code.InteractiveConsole.interact(self, banner) #PyV2&V3
     
    from PyQt4.QtCore import (Qt, pyqtSlot)
    from PyQt4.QtGui import (QTextEdit, QTextCursor, QApplication)
     
    class TextEdit(QTextEdit):
     
        _cursor = None
        _interpreter = None
     
        @property
        def cursor(self):
            if self._cursor is None:
                self._cursor = QTextCursor(self.document())
            return self._cursor
     
        def _moveToEnd(self):
            self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
     
        @pyqtSlot(str)
        def raw_input(self, prompt):
            log.debug('console.raw_input')
            self.write(prompt)
            self._start = self.cursor.position()
     
        @pyqtSlot(str)
        def write(self, data):
            log.debug('console.write, data=/%s/' % data)
            [ self.append((line)) for line in data.split('\n') if line ]
            self._moveToEnd()
     
        @pyqtSlot(str)
        def quit(self):
            log.debug('console.quit')
            self.close()
     
        def keyPressEvent(self, event):
            if event.key() in (Qt.Key_Return, Qt.Key_Enter):
                log.debug('console._on_eol')
                # maybe there is a clever way to get line to send
                cursor = self.cursor
                self._moveToEnd()
                eol = cursor.position()
                count = eol - self._start
                cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, count)
                line = cursor.selection().toPlainText()
                cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, count)
                if PyV2:
                    line = unicode(line) if len(line) else u''
                log.debug('interpret line: /%s/' % line)
                self._interpreter.interpret_line(line)
     
            else:
                QTextEdit.keyPressEvent(self, event)
     
     
    if __name__ == '__main__':
     
        app = QApplication(sys.argv)
     
        # setup console & proxy
        textEdit = TextEdit()
        interpreter = Interpreter(textEdit)
     
        # setup QThread
        task = QThread()  
        task.run = interpreter.interact # mock run
        interpreter.moveToThread(task) # move QObject to thread
     
        textEdit.show()   # show display
        task.start()     # start interpreter
        app.exec_()      # start event loop
     
        log.debug('terminating task')
        task.terminate()
        task.wait()
        log.debug('exit')
    Je n'aime pas passer par les QThread.
    Je comprends que le programmeur C++ apprécie d'avoir une API 'thread' qui soit homogène sur Windows, OSX, Linux, ...

    Mais le programmeur Python dispose déjà du module threading pour faire çà.
    Reste l'intégration signal/slot... Mais si c'est intéressant pour le programmeur C++, je ne pense pas que cela soit indispensable pour le programmeur Python.
    Dans le cas particulier, si l'interface entre GUI et Interpréteur passe par QThread, faire fonctionner cela avec d'autres GUI demande à revoir une bonne partie de l'interface. Ce qui est plutôt dommage car:
    1. Chaque modèle de threading étant particulier, chaque "portage" devra trouver des compromis pour que çà tombe en marche,
    2. De fait, la mécanique est difficilement ré-utilisable.

    Si j'ai du temps, je regarderais çà.
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  11. #11
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks,

    Merci, c'est très intéressant.

    Je n'ai pas réussi à le faire fonctionner sous Python 2.7, malgré les 2 modifications évidentes (io => StringIO et queue => Queue). Après, j'ai une erreur à la ligne "count = eol - self._start" (QTextEdit n'a pas d'attribut _start). Ensuite, la fenêtre s'affiche, mais elle reste vide, et aucune erreur n'est générée. Et comme je n'ai pas trouvé de script "3to2" , je ne sais plus quoi faire, sauf à éplucher ligne par ligne et comparer les 2 docs.

    Par contre, ça marche sous Python 3.2.

    Mon choix concernant l'utilisation de QThread est:

    1- il faut un thread pour que l'exécution d'un code dans l'interpréteur ne fige pas la partie graphique

    2- comme le thread ne doit pas toucher au graphique avec PyQt4, la communication passe par des messages: d'où l'utilisation de QThread au lieu de threading.

    Ton code est très différent du mien et il me faudra un peu de temps pour bien le comprendre.

    Merci! Je reste à l'écoute: n'hésite pas à rajouter des infos sur le sujet si tu en as.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  12. #12
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut Tyrtamos,

    Citation Envoyé par tyrtamos Voir le message
    Je n'ai pas réussi à le faire fonctionner sous Python 2.7, malgré les 2 modifications évidentes (io => StringIO et queue => Queue). Après, j'ai une erreur à la ligne "count = eol - self._start" (QTextEdit n'a pas d'attribut _start).
    Il y a des petits trucs agaçants concernant l'appel des méthodes dans les surclasses et remonter de l'unicode. J'ai posté une nouvelle mouture qui fait ce qu'il faut dans le cas PyV2 et PyV3.

    Mon choix concernant l'utilisation de QThread est:

    1- il faut un thread pour que l'exécution d'un code dans l'interpréteur ne fige pas la partie graphique

    2- comme le thread ne doit pas toucher au graphique avec PyQt4, la communication passe par des messages: d'où l'utilisation de QThread au lieu de threading.
    L'interpréteur "attend" la prochaine commande pour interfacer çà avec l'event loop d'un GUI, il faut du "threading" ou un process séparé.
    Le choix du process séparé est tentant car à première vue on rediriger stdin, stdout, stderr est "assez simple" dans ce cas. Mais si on y regarde de plus près, .raw_input signalant que l'interpréteur est prêt à recevoir la prochaine commande. Je ne vois pas comment réaliser cela sans passer par une sorte de RPC.


    Ton code est très différent du mien et il me faudra un peu de temps pour bien le comprendre.
    Techniquement, çà fait la même chose.
    Seule la réalisation de la répartition des rôles entre Interpreteur et TextEdit(GUI):
    • une Queue pour synchroniser les remontées,
    • utilisation de la mécanique signal/slot d'un QObjet vers un autre QObject lancés dans des threads différents

    Ce qui dégage les buffers verrouillés par des mutex de ton code.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  13. #13
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Je suis arrivé à me décider quant à l'interface entre threading et Qapp.

    Chaine Interpreter ---> Proxy ---> Widget
    L'interpréteur tranforme les appels aux méthodes .write, .raw_input, .quit en
    proxy.post(name, *args) ou name est le nom de la 'méthode'.
    L'interface entre Interpreter et le GUI est réalisée par le Proxy qui se
    débrouille pour appeler la méthode souhaitée du Widget en passant par
    l'event loop.

    Chaine Widget --> Interpreter
    Techniquement, il faudrait un autre proxy et réaliser l'équivalent d'une
    event loop (via Queue). En fait, les seules informations à remonter sont
    * la ligne à interpréter: çà se fait via une Queue.
    * abort: pour transporter le control-C mais là on passe via le backdoor
    ._async_raise de la thread.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    import sys
    import code
    if PyV3:
        from io import StringIO 
    elif PyV2:
        from io import BytesIO as StringIO 
     
    class Interpreter(code.InteractiveConsole):
        _thread = None
     
        # don't recycle sio
        def runcode(self, code):
            """redirect outputs to StringIO"""
            std_sav = sys.stdout, sys.stderr
            sys.stdout = sys.stderr = sio = StringIO()
            sio.write = self.write
            try:
                exec (code, self.locals)
            except SystemExit:
                self.quit()
            except:
                self.showtraceback()
            finally:
                sio.close()
                sys.stdout, sys.stderr = std_sav
     
        def __init__(self, proxy, locals=None, filename="<console>"):
            code.InteractiveConsole.__init__(self, locals, filename)
            self._inputQ = Queue(maxsize=1)
            self.post = proxy.post
            proxy.set_interpreter(self) # backlink for 'interpret_line'
     
        def interpret_line(self, line):
            log.debug('interpreter.interpret_line, /%s/' % line)
            self._inputQ.put(line)
     
        def write(self, data):
            log.debug('interpreter.write')
            self.post('write', data)
     
        def raw_input(self, prompt):
            log.debug('interpreter.raw_input')
            self.post('raw_input', prompt)
            data = self._inputQ.get()
            return data
     
        def quit(self):
            log.debug('interpreter.quit')
            self.post('quit')
     
        def interact(self, banner=None):
            """save current thread to make .abort happy"""
            self._thread = threading.current_thread()
            code.InteractiveConsole.interact(self, banner)
            log.debug('*** interact done')
     
        def abort(self):
            log.debug('interpreter.abort')
            tp = self._thread
            assert tp is not None
            tp._async_raise(KeyboardInterrupt)
    Proxy

    Le proxy est un QObject associé au widget TextEdit lors de sa construction.
    Les appels à .post effectués par l'interpréteur étant faits dans le mauvais thread, le boulot de Proxy est de les emballer pour qu'ils soient postés dans l'event loop de la QApp et qu'en sortie, çà exécute la méthode attendue côté widget.

    Pleins de méthodes Qt pour faire çà: j'ai choisi de passer par postEvent.
    AsyncCommand permet d'emballer la méthode à appeler dans un QEvent.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    from PyQt4.QtCore import (Qt, QObject, QEvent, QCoreApplication)
     
    class AsyncCommand(QEvent):
        def __init__(self, func, args):
            QEvent.__init__(self, QEvent.User)
            self.data = (func, args)
     
        def __call__(self):
            func, args = self.data
            try:
                rs = func(*args)
            except Exception as e:
                log.exception('error calling %s\%s' % (func.__name__, e))
     
    class QtProxy(QObject):
     
        def set_interpreter(self, obj):
            self._widget._interpreter = obj
     
        def __init__(self, widget):
            QObject.__init__(self)
            self._widget = widget
            self._postEvent = QCoreApplication.instance().postEvent
     
        def post(self, name, *args):
            log.debug('post: thread.ident = %d' % threading.current_thread().ident)
            log.debug('post(%s, ...)' % name)
            postEvent = self._postEvent
            w = self._widget
            command = AsyncCommand(getattr(w, name), args)
            postEvent(self, command)
     
        def event(self, command):
            log.debug('event: thread.ident = %d' % threading.current_thread().ident)
            if isinstance(command, AsyncCommand):
                command()
                return True
            else:
                return QObject.event(self, event)
    Ajouts cosmétiques: support du control-C
    Maintenant qu'on a des threads Python, on peut hacker!
    Dans le code, il y a une s/classe de Thread qui définit ._async_raise et .terminate. thread._async_raise(KeyboardInterrupt) est correctement propagé. Lorsque l'interpréteur "boucle", çà met du temps à être pris en compte. Il faut être patient pour arrêter un "while 1: print('loop')", il est préférable d'avoir "while 1: print('loop'); time.sleep(0.1)"

    - W
    Fichiers attachés Fichiers attachés
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  14. #14
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Juste pour montrer que çà s'interface pas trop mal avec Tk.
    Il suffit d'écrire le TkProxy et le TextEdit.
    Dans __main__, seulement, le démarrage de l'application et l'appel de la mainloop changent.

    Le code est:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    if PyV3:
        import tkinter as tk
    elif PyV2:
        import Tkinter as tk
     
    class TkProxy(object):
     
        def set_interpreter(self, obj):
            self._widget._interpreter = obj
     
        def __init__(self, widget):
            self._widget = widget
            self._postEvent = widget.after_idle
     
        def post(self, name, *args):
            log.debug('post: thread.ident = %d' % threading.current_thread().ident)
            log.debug('post(%s, ...)' % name)
            w = self._widget
            postEvent = self._postEvent
            postEvent(getattr(w, name), *args)
     
     
    class TextEdit(tk.Text):
     
        def __init__(self, parent=None, cfg=None, **kwds):
            cfg = cfg or {}
            tk.Text.__init__(self, parent, cfg, **kwds)
            self.bind('<KeyPress>', self.on_keyPress)
     
        def write(self, data):
            log.debug('widget.write')
            self.insert('insert', data)
     
        def raw_input(self, prompt):
            log.debug('widget.raw_input')
            self.write(prompt)
            self._start = self.index('insert')
     
        def on_keyPress(self, event):
     
            if event.keysym in ('Return', 'KP_Enter'):
                log.debug('widget: on eol')
                line = self.get(self._start, self.index('end'))
                self._interpreter.interpret_line(line)
            elif event.keysym in ('c', 'C') and event.state & 0x0004:
                log.debug('widget.control-C')
                self._interpreter.abort()
                return 'break'
     
    if __name__ == '__main__':
     
        app = tk.Tk()
     
        # setup widget, proxy, then console
        textEdit = TextEdit(width=80, height=24)
        proxy = TkProxy(textEdit)
        interpreter = Interpreter(proxy)
     
        # setup QThread
        task = Thread(target=interpreter.interact)  
        textEdit.pack()   # show display
        task.start()     # start interpreter
        tk.mainloop()      # start event loop
     
        log.debug('terminating task')
        task.terminate()
        task.join()
        log.debug('exit')
    Il est certain que le postEvent tk est beaucoup plus simple à écrire: tk et Python échangent des "str" et pas de trucs qui doivent être mis dans un format C++.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  15. #15
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks,

    Merci pour les compléments! Je n'aurais jamais imaginé mettre un proxy là-dedans, mais bon.

    Il me faudra un peu de temps pour digérer tout ça...
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  16. #16
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut Tyrtamos,

    Il me faudra un peu de temps pour digérer tout ça...
    Désolé.
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  17. #17
    Membre chevronné

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Points : 1 752
    Points
    1 752
    Par défaut
    Bonjour,
    très intéressant ce code. Je n'ai vu que le fonctionnement général sans entrer dans les détails. J'ai deux questions.
    1. Est-il envisageable de faire de la coloration syntaxique ?
    2. A-t-on la main sur ce qui est taper dans la console ?

  18. #18
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Citation Envoyé par rambc Voir le message
    Bonjour,
    très intéressant ce code. Je n'ai vu que le fonctionnement général sans entrer dans les détails. J'ai deux questions.
    1. Est-il envisageable de faire de la coloration syntaxique ?
    2. A-t-on la main sur ce qui est taper dans la console ?
    Est-il envisageable de faire de la coloration syntaxique ?
    Pour le code Tk, on devrait pouvoir récupérer le TextEdit d'IDLE qui sait faire.
    A-t-on la main sur ce qui est taper dans la console ?
    Tout ce qui est tapé passe par la méthode .keyPress qui détecte fin de lignes et control-C (ou plus dans le code de Tyrtamos) => à partir de là, o peut tout imaginer
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  19. #19
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Puisque j'en avais besoin rapidement, j'ai mis à jour mon tuto:

    http://python.jpvweb.com/mesrecettes...console_python

    Dans ce code, je n'ai pas encore exploité les derniers codes de wiztricks, à part quelques détails.

    Il est écrit en Python 2.7, mais j'ai pu vérifier qu'avec le script 2to3 et quelques modifs manuelles, il était assez facile de le convertir en Python 3.x.

    J'ai aussi mis à jour le tuto qui permet de lancer, entre autres, cette console à partir d'une icone située dans la zone de notification (system tray):

    http://python.jpvweb.com/mesrecettes...t4_system_tray

    Avec ces 2 logiciels mis ensemble, et en plus traités par cx_freeze, j'ai donc une console qui s'installe au boot, qui est toujours dispo (clic sur l'icone), et je commence à l'enrichir avec les modules spécialisés qui m'intéressent.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  20. #20
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Juste une petite info concernant la possibilité d'ajouter la coloration syntaxique à la console interactive Python écrite en PyQt4.

    Il existe QScintilla qui est un éditeur de texte qui a ça. Il est installé avec PyQt4 sous Windows, et j'imagine qu'on doit aussi le trouver facilement dans les autres OS (c'est ok pour Ubuntu/Mint14). Je crois qu'il est utilisé par l'outil de développement "eric".

    On trouve ici un exemple d'un éditeur mini QScintilla en PyQt4:

    http://eli.thegreenplace.net/2011/04...lla-with-pyqt/

    Le manuel de QScintilla se trouve ici: http://pyqt.sourceforge.net/Docs/QScintilla2/index.html

    Il est possible, mais je n'ai pas essayé, qu'on puisse utiliser une partie de ses fonctionnalités pour faire de la coloration syntaxique avec la console interactive en Python écrite en PyQt4.

    Il ne reste plus qu'à essayer ...
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Comment ouvrir une console Python dans Eclipse
    Par ptissendier dans le forum Eclipse
    Réponses: 2
    Dernier message: 24/02/2014, 11h12
  2. Réponses: 5
    Dernier message: 19/01/2011, 13h47
  3. Insérer une fenêtre Python dans un canevas Tcl
    Par astro824 dans le forum Tkinter
    Réponses: 0
    Dernier message: 13/07/2010, 15h28
  4. Definition d'une classe python dans plusieurs fichiers
    Par wfargo dans le forum Général Python
    Réponses: 3
    Dernier message: 05/12/2006, 23h03
  5. Réponses: 3
    Dernier message: 26/06/2006, 13h48

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo