lundi 8 juin 2026

Ubuntu : downgrade NVIDIA drivers

 


Ubuntu : downgrade NVIDIA drivers


Récemment, après une mise à jour, mon écran principal (DELL 30 pouces dual-DVI) n'affiche plus rien, mon écran secondaire (DELL 24 pouces 16/10) a une résolution inférieure à celle par défaut (1920*1080 au lieu de 1920 x 1200).

En recherchant dans le journal de démarrage (ouvrir une console et lancer : sudo dmesg), je note les messages suivants :

NVRM: The NVIDIA GeForce GTX 650 GPU installed in this system is supported through the NVIDIA 470.xx Legacy drivers. Please visit http://www.nvidia.com/object/unix.html for more
NVRM:  information.  The 535.309.01 NVIDIA driver will ignore
NVRM:  this GPU.  Continuing probe...
NVRM: No NVIDIA GPU found.

Explication : apparemment, la dernière mise à jour a installé un driver NVIDIA version 535 à la place du 470, et cette version ne supporte pas ma carte graphique.

Effectivement, d'après cette page, l'ancienne carte GTX 650 n'est plus supportée dans la version 535.

Le driver est donc incapable de piloter ma carte graphique, et adopte un mode dégradé :

  • incapacité à piloter un écran dual-DVI
  • incapacité à afficher en 1920 x 1200

Petit indice supplémentaire : le gestionnaire de mises à jour (image ci-dessus) propose bien un driver version 535, mais le choix est grisé.

Il va donc falloir revenir en arrière et installer une version 470.

Voici la méthode habituelle, dans un terminal :

sudo apt-get purge 'nvidia*'
sudo apt autoremove
sudo apt install nvidia-driver-470

Mais voilà, même après un redémarrage, le driver 535 est toujours présent.

Après quelques recherches, il s'avère qu'il faut redémarrer la machine avant d'installer le driver 470.

Je recommence donc l'opération :

sudo apt-get purge 'nvidia*'
sudo apt autoremove
sudo reboot (ou bouton redémarrer)
sudo apt install nvidia-driver-470
sudo reboot (ou bouton redémarrer)

Après le premier redémarrage, comme aucun driver NVIDIA n'est installé, le driver nommé Nouveau est utilisé, les deux écrans fonctionnent, mais ce driver n'est pas très rapide et produit des artefacts, des traces de pointeur de souris qui polluent l'affichage.

Après l'installation du driver 470 et le deuxième redémarrage, tout est rentré dans l'ordre. Le gestionnaire de mises à jour (image ci-dessus) propose bien un driver version 470, actif.

Bien entendu, ce cas de figure n'est qu'un exemple parmi tant d'autres. Les raisons pour lesquelles on peut être dans l'obligation d'installer une version précédente (downgrade) du driver NVIDIA sont diverses et variées (plantages avec certaines cartes, bugs, etc.).

Dans ce genre de cas, il y a des commandes indispensables à connaître :

Afficher les messages de démarrage :

sudo dmesg

Afficher la liste des cartes présentes :

lspci

Afficher la liste des drivers chargés :

lsmod

Afficher les logiciels NVIDIA installés :

aptitude search nvidia

Afficher l'onglet "Pilotes Additionnels" du gestionnaire de mises à jour :

Cliquer sur "Gestionnaire de mises à jour" dans la barre latérale.
Cliquer sur le bouton "Paramètres".
Choisir l'onglet "Pilotes Additionnels"


Et surtout : restez curieux !


Cordialement

Henri


Mai 2026 : Actualité des Blogs du Mois

   


Actualité des Blogs du Mois


Sur  Framboise 314 :

Sur  MCHobby :

Sur TutoDuino :

Les indémodables :

J'aurais dû en parler depuis longtemps :


    Cordialement

    Henri 


    dimanche 10 mai 2026

    Batteries : décharge profonde

     


    Batteries : Décharge profonde


    C'est un phénomène bien connu. La décharge profonde est souvent synonyme de destruction, qu'il s'agisse de batteries NI-MH, LITHIUM-ION ou LIPO.

    Je viens malheureusement d'en faire l'expérience avec quelques appareils. Après une longue période d'inactivité, certaines batterie se sont déchargées complètement.

    Nous allons parler de ces appareils en les catégorisant.

    1. Batteries stockées

    Quelques batterie neuves LIPO et LITHIUM-ION stockées depuis 6 ans ont parfaitement résisté. Leur tension est encore de 3.5V à 3.6V. Cela ne veut pas dire bien sûr qu'une batterie usagée résistera aussi bien.

    Il est à noter que les batteries de ce type ne sont pas vendues chargées à bloc, ce qui explique probablement leur bon comportement pendant un stockage prolongé. D'après quelques sources, leur tension de stockage est de 3.8V, ce qui améliore leur stabilité dans le temps. Cela semble parfaitement vrai.

    Qu'en est il de l'état d'une batterie neuve ayant été chargée à bloc, donc 4.2V, et stockée ensuite ? D'après ce que j'observe la tension retombe naturellement à 3.8V, et ne diminue plus, ou beaucoup plus lentement. On peut donc affirmer que charger à 100% une batterie neuve pour la stocker ensuite ne sert absolument à rien.

    2. Appareils sans détection de décharge profonde

    2.1. Testeur de composants

    Le premier appareil n'ayant pas démarré après une longue période d'inactivité a été ce testeur de composants :

    Il est à noter qu'il ne possède pas d'interrupteur marche/arrêt. Un simple bouton poussoir permet de démarrer la mesure. Cela veut clairement dire que le processeur (un ATMEGA328) est sous tension en permanence, en veille profonde. Lorsqu'il est laissé inutilisé la batterie LIPO se vide peu à peu, très peu certes, mais suffisamment pour détruire la batterie en quelques mois ou années.

    Après démontage, il s'avère que la tension de la batterie est de ZÉRO volts. Il n'y a aucune chance de pouvoir la recharger. Le remplacement de celle-ci a été nécessaire :

    Le testeur a redémarré sans problème.

    A noter qu'au démarrage, l'appareil affiche la tension de sa batterie. Le test périodique de celle-ci est donc simple.

    2.2. Téléphone DECT

    Lorsqu'il est posé sur son socle, un téléphone DECT est rechargé automatiquement. Souvent il est équipé de batteries NI-MH. Pour l'instant, après 13 ans de service, j'ai été obligé de changer les batteries une seule fois, ce qui prouve que ces appareils sont assez bien conçus d'un point de vue recharge.

    2.3. Appareil photo, téléphone mobile

    Mon appareil photo Panasonic ne démarre pas non plus. Il est vrai qu'il a été pas mal sollicité depuis son achat (2009), et que la batterie est très âgée. Sa tension a chuté à 2.85V, ce qui est limite mais pas catastrophique. Une simple recharge suffira.

    Normalement, un téléphone mobile entre en veille profonde lorsque la tension de sa batterie chute en dessous de quelques %. Ce n'est pas une raison pour le laisser dans cet état pendant trop longtemps. La veille profonde consomme un peu d'énergie tout de même.

    2.4. Clavier, souris, télécommande infrarouge

    Je remplace systématiquement les piles de ces appareils par des batteries NI-MH, ce qui est économiquement et écologiquement une bonne chose. Mais c'est aussi une mauvaise chose, car les batteries NI-MH ont un courant de fuite supérieur, donc une autonomie plus faible que les piles.

    Ici, le test est simple. Il suffit d'essayer ... Si la batterie est trop faible l'appareil ne fonctionnera pas, ou mal, ou aura une portée réduite.

    Il est normalement assez rare de laisser de genre d'appareil inutilisé. Dans ce cas, il vaut mieux retirer les batteries.

    3. Appareils avec détection de décharge profonde

    Je possède un seul type d'appareil pourvu d'un mécanisme de test de la tension de la batterie, avec avertissement sonore : 3 détecteurs de fumée Bosch, qui émettent un bip à intervalle régulier en cas de tension batterie trop faible, ce qui semble assez normal, étant donné qu'il s'agit d'un élément de sécurité.

    Il y a donc en théorie très peu de chances pour que les batteries entrent en décharge profonde, à moins d'être absent du domicile pendant une longue période. En cas de longue absence, il faudra surveiller régulièrement l'état des batteries. Un bouton de test est prévu à cet effet.

    A noter que ces détecteurs Bosch, en plus de leur rôle de surveillance permanente de la présence de fumée, sont interconnectés, donc communiquent entre eux par radio. Une belle performance en matière de consommation d'énergie !

    4. Conclusion

    Un seul mot pour conclure : vigilance !

    On ne laisse pas un appareil alimenté par batterie inutilisé pendant une longue période sans vérifier périodiquement l'état de celle-ci.


    Cordialement

    Henri


    mercredi 25 février 2026

    Processeur AMD : sauvetage



     

    Processeur AMD : sauvetage


    Ce mois-ci, en montant un PC pour un ami, à base de processeur AMD Ryzen 5 3400G, j'ai eu une mésaventure. J'ai par erreur monté le ventirad à l'envers :

    1. L'accident

    La marque AMD, située ici en haut, s'est retrouvée du côté des barrettes de RAM, et interdisait de mettre en place la première barrette. Oups ...

    Il a fallu démonter le ventirad pour le tourner de 180°. Mais le processeur est resté collé au ventirad. J'ai été obligé de le décoller l'aide d'un tournevis. En le remettant en place dans le support, j'ai dû forcer légèrement.

    A la première mise en route le PC n'a pas démarré. Il a fallu démonter à nouveau. Et là, je constate qu'une des broches en périphérie est tordue (au passage il y en a 1331 !).

    2. La réparation

    Les broches sont très fines et espacées de 1mm seulement. Il va falloir utiliser une paire de pinces brucelles extra fines et une loupe frontale, matériel que je possède déjà, heureusement :


    La broche est tordue à angle droit, plaquée sous le processeur, il s'agit de la soulever avec précaution avec un bec de la pince brucelle, et ensuite de la détordre, sans la casser, et sans déformer les broches voisines. Eh bien, contre toute attente, j'ai réussi ! le processeur a été remonté avec sont ventirad, et le PC a démarré. Ouf.

    3. Conclusion

    Les broches d'un processeur sont suffisamment molles pour être redressées sans trop de risque. Il s'agit probablement de cuivre pur, plaqué or.

    Conseil : lors de la mise en place d'un processeur, si celui-ci ne s'enfonce pas très facilement dans le support (socket), il ne faut surtout pas forcer. Si j'avais examiné de près le dessous du processeur, j'aurais très certainement constaté qu'une broche était légèrement tordue, et cela m’aurait évité de procéder à cette opération risquée et délicate.

    L'aspect économique est certes à prendre en compte. Le prix de la loupe frontale et des pinces brucelles est équivalent à celui du processeur. Si l'on est pas déjà équipé de ce matériel, cela peut ne pas être rentable.


    Cordialement

    Henri



    dimanche 7 décembre 2025

    PYTHON : Initiation

     


    PYTHON : Initiation


    Ce mois-ci je propose une petite initiation au langage Python. Le but ici n'est pas de produire un tutoriel exhaustif, déjà très nombreux sur le WEB, mais de donner envie d'utiliser ce langage moderne.

    La première version de Python est sortie en 1991. La version 2 date de 2000, la version 3 de 2008. C'est à l'heure actuelle un des langages les plus utilisés, aussi bien sur PC que sur plateforme embarquée. Pour ma part, Python représente une grande part des lignes de code que j'ai écrites aussi bien dans ma vie privée que professionnelle.

    Python peut être utilisé pour une grande variété de projets :

    • automatisation
    • projets scientifiques
    • serveurs WEB
    • bases de données
    • communication
    • interface graphique
    • etc.

    Sur ce blog il suffit de rechercher le mot "python" pour constater que j'ai déjà publié pas mal de petits scripts.

    1. Cibles

    Les plateformes sur lesquelles peut tourner Python sont nombreuses :

    • machine Windows, MacOs, Linux
    • cartes SBC comme la Raspberry PI, BeagleBone, Banana PI

    Mais il existe aussi des versions de Python légères, comme MicroPython, qui peuvent se contenter de plateformes moins puissantes comme les ESP32, STM32.

    Le principal intérêt de Python sur des plateformes aussi variées est la portabilité et on comprend vite l'intérêt d'utiliser un langage unique : limiter la formation.

    A l'heure actuelle je développe essentiellement avec :

    • sur PC : Python, HTML, JavaScript
    • sur RASPBERRY PI : Python, HTML, JavaScript
    • sur ESP8266, ESP32, ARDUINO : C, C++, HTML, JavaScript

    2. Installation

    L'installation de Python est simple.

    Sous Linux, en fonction de la distribution, il est souvent pré-installé. Pour le vérifier, lancer la commande suivante :

    python -V

    S'il est installé il va afficher sa version :

    Python 3.12.3

    Si ce n'est pas le cas, il faut utiliser la méthode d'installation de la distribution :

    Sous Ubuntu : sudo apt-get install python 

    Sous Fedora : sudo dnf install python3

    Sous Windows il faudra télécharger le logiciel d'installation ici.

    Sous Windows n'oubliez pas de cocher la case "Add Python 3.XX to PATH" :

    2.1. Installation de packages

    Installer le langage Python ne veut pas dire que l'on installe simplement un interpréteur nu avec des instructions basiques. Un nombre très important de librairies sont présentes : The Python Standard Library

    Par contre d'autres librairies peuvent vous apporter des fonctionnalités avancées. Par exemple, on peut très bien développer un serveur WEB à l'aide du package http.server, présent dans la librairie standard, mais c'est un peu comme travailler avec un marteau et une enclume. Pour développer des application plus cossues, on a souvent recours à des frameworks comme Jango, ou CherryPy.

    2.2. Pip

    Certains sites proposent l'installation de leurs packages à l'aide de pip (Package Installer for Python).

    C'est en effet un procédé très simple, qui permet d'installer des packages sans avoir à télécharger une archive et se préoccuper de l'endroit où il va falloir la décompresser.

    Sous Ubuntu ou Debian : sudo apt install python3-pip

    Sous Windows : Comment installer PIP sur Windows

    On pourra ainsi installer par exemple le package de communication série PySerial comme ceci :

    pip install pyserial

    3. La console

    Python met à votre disposition une console qui vous permettra d'exécuter du code sans avoir à créer de fichier source.

    Il suffit pour cela de taper la commande python dans un terminal :

    python

    ou (sous Windows)

    py

    Python 3.12.3 (main, Nov  6 2025, 13:44:16) [GCC 13.3.0] on linux

    Type "help", "copyright", "credits" or "license" for more information.

    >>>

    Ensuite on entre le code à exécuter : 

    print("Hello Python")
    Hello Python
    >>> 

    On quitte la console en tapant CTRL-D.

    Cette console est un moyen très pratique de taper quelques lignes de code rapidement pour faire des essais.

    4. Un tutoriel ?

    Il existe énormément de tutoriels sur le langage Python. Je vous propose le tutoriel officiel pour démarrer.

    Celui de W3school, en anglais, aborde des sujets supplémentaires :

    • mathplotlib
    • fichiers
    • base de données MySQL
    • etc.

    Attention : il est assez déconseillé d'adopter de vieux tutoriels Python2 pour travailler en Python3, car les différences sont assez importantes.

    5. Quel éditeur ?

    Pour écrire du code, on utilise un éditeur de texte. Certains éditeurs évolués comme Visual Studio Code vous apporteront un certain confort, en particulier la coloration syntaxique.

    L'écriture de code en Python n'utilise pas d'accolades pour définir les blocs de code comme en C ou C++, mais une indentation.

    En C, on utilise des accolades :

    for (i=0 ; i < 4 ; i++)
    {
        printf("blabla\n");
        printf("%d\n", i);
    }

    L'équivalent en Python :

    for i in range(4):
        print("blabla")
        print(i)

    Ce code produira le résultat suivant :

    blabla
    0
    blabla
    1
    blabla
    2
    blabla
    3

    Mais si vous écrivez :

    for i in range(4):
        print("blabla")
    print(i)

    Ce code produira le résultat suivant :

    blabla
    blabla
    blabla
    blabla
    3

    Attention donc.

    Pour l'indentation il va falloir faire un choix. Soit vous utilisez la tabulation, soit vous utilisez des espaces. La majeure partie des éditeurs modernes permettent d'insérer des espaces lorsque l'on insère une tabulation. En général, la taille d'une tabulation est configurable. Un nombre de 3 ou 4 espaces est en général une bonne valeur.

    6. Domaines d'application

    Les domaines d'application sont très nombreux. Je n'ai pas l'intention de tous les énumérer, mais en voici quelques exemples :

    6.1. Instructions de base

    Quand on débute en Python, on constate assez vite que la simplicité est au rendez-vous. De plus, Python emprunte au langage C certaines caractéristiques essentielles, mais aussi à JavaScript :

    Commentaire :

    # comment

    Commentaire sur plusieurs lignes :

    '''
    comment using string literals
    with triple quotes
    '''

    Déclaration d'un entier :

    a = 100

    Déclaration d'un entier en notation hexadécimale :

    a = 0x44

    Déclaration d'un caractère en notation hexadécimale :

    a = "\x44"

    Déclaration d'une chaîne de caractères :

    s = "this is a string"
    s = 'this is a string'
    a = "\x44\x33"

    On peut accéder à un caractère particulier dans la chaîne, comme en C :

    t = "string"
    print(t[2])

    Les tableaux :

    t = [0, 1, 2, 3]
    print(t[2])

    t = (0, 1, 2, 3)
    print(t[2])

    On remarque deux types de tableaux : le premier, appelé liste, modifiable (entre crochets), le deuxième, appelé tuple, non modifiable (entre parenthèses).

    Tous les tableaux sont indexés en partant de ZÉRO, comme en C.

    En Python on peut ajouter, modifier ou supprimer un élément de liste :

    t = [0, 1, 2, 3]
    t.append(5)    # ajoute un élément dont la valeur est 5
    t[4] = 12        # modifie l'élément 4
    t.pop(2)         # retire l'élément 2

    Au final, le contenu sera : [0, 1, 3, 12]

    Appel de fonction ou de méthode :

    function(arguments)
    object.method(arguments)

    Un dictionnaire, que l'on peut assimiler à une structure C :

    x = {"name" : "John", "age" : 36}

    Les habitués du C ou de JavaScript ne seront donc pas complètement perdus.

    On remarque que le type des variables n'est pas explicitement déclaré.

    On peut parfaitement écrire :

    a = 100
    a = "abcdef"

    La variable a change donc de type à volonté. C'est un point qui m'a fortement perturbé à mes débuts. On m'a répondu "simple question d'ouverture d'esprit". J'ai donc arrêté de me plaindre et me suis accroché.

    En fait chaque fonction est libre de contrôler ses arguments :

    range("3")

    Produit une exception :

    TypeError: 'str' object cannot be interpreted as an integer

    6.1.1. Boucle for

    En C on écrirait :

    for (i=0 ; i < 4 ; i++)
    {
        printf("i a pour valeur %d\n", i);
    }

    L'équivalent en Python :

    for i in range(4):
        print("i a pour valeur", i)

    Dans cet exemple les différences sont flagrantes :

    • pas d'accolades mais une simple indentation (tabulations ou espaces)
    • pas de point-virgule

    6.1.2. Séquence

    On l'aura compris, la fonction range() retourne une séquence de nombres, 0 à 4 dans l'exemple précédent.

    range() peut prendre 3 arguments :

    range(3, 10, 2) # retourne une séquence de 3 to 9, par pas de deux : [3, 5, 7, 9]

    Si on désire une séquence sans logique précise, on peut l'écrire comme ceci :

    for i in [0, 2, 4, 8, 13]:
        print("i a pour valeur", i)

    [0, 2, 4, 8, 13] est aussi une séquence. On remarque déjà qu'en C ou C++ il serait plus difficile d'écrire l'équivalent.

    c = ["Un", "problème", "sans", "solution", "est", "un", "problème", "mal", "posé"]

    for i in range(len(c)):
        print("i vaut", i, "et c[i] vaut", c[i])

    6.1.3. Typage

    Dans les exemples précédents on a vu que [0, 2, 4, 8, 13] et ["Un", "problème", "sans", "solution", "est", "un", "problème", "mal", "posé"] sont des séquences.

    Peut on mélanger les chaînes de caractères et les nombres dans une séquence ?

    c = [1, "problème", "sans", "solution", "est", 1, "problème", "mal", "posé"]

    Oui. Cette déclaration est tout à fait valide.

    Il est même possible de déterminer le type de chaque élément :

    c = [1, "problème", "sans", "solution", "est", 1, "problème", "mal", "posé"]
    print(c)
    for i in range(len(c)):
        print(type(c[i]))

    Ce qui produit le résultat suivant :

    [1, 'problème', 'sans', 'solution', 'est', 1, 'problème', 'mal', 'posé']
    <class 'int'>
    <class 'str'>
    <class 'str'>
    <class 'str'>
    <class 'str'>
    <class 'int'>
    <class 'str'>
    <class 'str'>
    <class 'str'>

    6.1.4. Slicing

    La simplicité est encore plus évidente quand on aborde le slicing (découpage) :

    alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    print(alpha[4:16])

    Ce qui produit le résultat suivant :

    EFGHIJKLMNOP

    En C on utiliserait une fonction comme strncpy() ou memcpy(), plus complexes et dangereuses pour les débutants, et qui nécessitent une allocation mémoire.

    En Python, les lignes suivantes :

    alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    print(alpha[20:100])

    Produisent le résultat suivant :

    UVWXYZ

    En clair, on a demandé une tranche de la chaîne s, située entre le 20ème caractère et le 100ème, alors que la chaîne a une longueur de seulement 26 caractères. On l'aura compris, Python est un langage qui apporte de la sécurité.

    Comment récupérer le dernier élément d'une séquence ? comme ceci :

    a = "abcdef"
    print(a[-1])

    6.1.5. Introspection

    On appelle introspection la capacité à examiner les classes, fonctions et mots-clés pour savoir ce qu'ils sont, ce qu'ils font et ce qu'ils connaissent.

    Écrivons une classe et un peu de code :

    class student:
        name = ""
        present = False
        # constructeur
        def __init__(self, name):
            self.name = name
        # methods
        def enter(self):
            ''' the student enters the school '''
            self.present = True
        def exit(self):
            ''' the student leaves school '''

            self.present = False

    john = student("john")
    print("john:")
    print(john)
    print("id(john):")
    print(id(john))
    print('dir("john"):')
    print(dir(john))
    print('john.__class__:')
    print(john.__class__)
    print('dir(john.present):')
    print(dir(john.present))
    print('dir(john.enter):')
    print(dir(john.enter))
    print('callable(getattr(john, "enter")):')
    print(callable(getattr(john, "enter")))

    Ce qui produit le résultat suivant :

    john:
    <__main__.student object at 0x7024a0d6ef00>
    id(john):
    126360425639824
    dir("john"):
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'enter', 'exit', 'name', 'present']
    john.__class__:
    <class '__main__.student'>
    dir(john.present):
    ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
    dir(john.enter):
    ['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
    callable(getattr(john, "enter")):
    True

    Examinons de plus près :

    print("john:")
    print(john)
    john:
    <__main__.student object at 0x7024a0d6ef00>

    print(john) dit que john est un objet de la classe student situé dans le module __main__, et que son identifiant est 0x7024a0d6ef00

    print("id(john):")
    print(id(john))
    id(john):
    126360425639824

    id(john) nous donne l'identifiant de l'objet john : 126360425639824 (0x7024a0d6ef00 en décimal)

    print('dir("john"):')
    print(dir(john))
    dir("john"):
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'enter', 'exit', 'name', 'present']

    dir(john) retourne les membres de l'objet john. Les méthodes que nous avons écrites sont présentes, parmi d'autres membres.

    print('john.__class__:')
    print(john.__class__)
    john.__class__:
    <class '__main__.student'>

    john.__class__ contient le nom de la classe de l'objet john : __main__.student

    print('dir(john.present):')
    print(dir(john.present))
    dir(john.present):
    ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']

    dir(john.present) retourne les attributs de l'attribut present de l'objet john.

    print('dir(john.enter):')
    print(dir(john.enter))
    dir(john.enter):
    ['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

    dir(john.enter) retourne les attributs de la méthode enter() de l'objet john.

    print('callable(getattr(john, "enter")):')
    print(callable(getattr(john, "enter")))
    callable(getattr(john, "enter")):
    True

    callable(getattr(john, "enter")) permet de savoir si le membre enter de l'objet john est appelable.

    On peut remarquer sur cette ligne l'utilisation de simples et doubles quotes imbriquées :

    print('callable(getattr(john, "enter")):')

    Ce qui permet de ne pas fermer la chaîne sur la première double quote de "enter".

    On aurait pu écrire aussi :

    print("callable(getattr(john, 'enter')):")

    Ou, comme en C :

    print("callable(getattr(john, \"enter\")):")

    En Python on peut indifféremment utiliser la simple ou la double quote pour encadrer une chaîne, comme en JavaScript.

    On constate qu'en Python tout est orienté objet, qu'il s'agisse de classe, d'instances, d'attributs et méthodes. Je vous laisse imaginer les possibilités offertes par toutes ces méthodes d'introspection.

    6.1.6. Et les pointeurs ?

    En Python, la notion de pointeur n'existe tout simplement pas. C'est certainement une bonne nouvelle pour les débutants.

    6.2. La ligne série

    Quand on travaille avec un ARDUINO ou un ESP, il est assez courant d'avoir recours au moniteur série pour afficher des information sur l'écran du PC.

    Mais c'est assez limité. Que faire si l'on a besoin d’interagir avec le logiciel embarqué ? La ligne série, accessible via le connecteur USB de la carte, semble le moyen le plus simple.

    J'ai déjà traité ce sujet ici : Commander un ARDUINO par la ligne série ou BLUETOOTH

    Pour communiquer en série à partir de Python, il vous faudra installer le package PySerial.

    Certains d'entre vous ont peut être déjà développé des logiciel de communication série en C à l'aide de Win32 sous Windows, ou termios sous Linux. Sous Windows, on peut très vite constater que cela peut très vite provoquer des problèmes en fonction de la cible. On logiciel peut très bien fonctionner sur certains PC et mal fonctionner sur d'autres (c'est du vécu). Il faut vraiment être un spécialiste de Win32 pour s'en sortir. Sous Linux c'est beaucoup moins le cas.

    Mais dans tous les cas, avec PySerial votre code fonctionnera aussi bien sur Windows que MacOs ou Linux. Seule l'ouverture de la ligne série diffère :

    Sous Linux :

    ser = serial.Serial('/dev/ttyUSB0')

    Sous Windows :

    ser = serial.Serial('COM1')

    6.3. L'interface graphique

    En matière d'interface graphique, on peut en Python se contenter de TkInter, qui produit des résultats honorables mais assez peu esthétiques :

    Interface graphique Tkinter python

    On peut également utiliser WxPython, PyGTK, ou PyQT, qui sont des interfaces Python à WxWindows, GTK et QT.

    On peut citer également PyGame, une librairie de dessin toute simple, sans widgets.

    Mais à l'heure actuelle ces librairies sont de moins en moins utilisées, la création d'interfaces graphiques se faisant principalement en HTML, JavaScript et CSS.

    6.4. Le serveur WEB

    Il existe une grande variété de serveurs WEB écrits en PYTHON :

    • http.server
    • CherryPy
    • Django
    • Flask
    • etc

    6.4.1. http.server

    http.server fait partie de la librairie standard Python. Examinons cet exemple simple :

    from http.server import BaseHTTPRequestHandler, HTTPServer
    import time
     
    hostName = "127.0.0.1"
    serverPort = 8080
     
    class MyServer(BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
            self.wfile.write(bytes("<body>", "utf-8"))
            self.wfile.write(bytes("<p>This is my web server</p>", "utf-8"))
            self.wfile.write(bytes("</body></html>", "utf-8"))
     
    if __name__ == "__main__":        
        webServer = HTTPServer((hostName, serverPort), MyServer)
        print("Server started at http://%s:%s" % (hostName, serverPort))
     
        try:
            webServer.serve_forever()
        except KeyboardInterrupt:
            pass
     
        webServer.server_close()
        print("Server stopped.")

    Si l'on lance ce serveur en local il affichera sur la console :

    Server started at http://127.0.0.1:8080

    Dans le navigateur on entrera :

    http://localhost:8080/index

    ou :

    http://localhost:8080/

    Le navigateur affichera simplement : This is my web server

    On peut ajouter une URL supplémentaire nommée alternative :

    from http.server import BaseHTTPRequestHandler, HTTPServer
    import time

    hostName = "127.0.0.1"
    serverPort = 8080

    class MyServer(BaseHTTPRequestHandler):
        def do_GET(self):
            if(self.path == '/' or self.path == '/index'):
                    self.send_response(200)
                    self.send_header("Content-type", "text/html")
                    self.end_headers()
                    self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
                    self.wfile.write(bytes("<body>", "utf-8"))
                    self.wfile.write(bytes("<p>This is my web server</p>", "utf-8"))
                    self.wfile.write(bytes("</body></html>", "utf-8"))
            elif(self.path == '/alternative'):
                    self.send_response(200)
                    self.send_header("Content-type", "text/html")
                    self.end_headers()
                    self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
                    self.wfile.write(bytes("<body>", "utf-8"))
                    self.wfile.write(bytes("<p>This is another URL</p>", "utf-8"))
                    self.wfile.write(bytes("</body></html>", "utf-8"))

    if __name__ == "__main__":
        webServer = HTTPServer((hostName, serverPort), MyServer)
        print("Server started at http://%s:%s" % (hostName, serverPort))

        try:
            webServer.serve_forever()
        except KeyboardInterrupt:
            pass

        webServer.server_close()
        print("Server stopped.")

    Dans le navigateur on entrera :

    http://localhost:8080/alternative

    Le navigateur affichera alors : This is another URL

    Comme on le voit, la librairie est assez spartiate. On a franchement l'impression de travailler à très bas niveau. 

    6.4.2. CherryPy

    Cherrypy propose une approche intéressante :

    import cherrypy
     
    class HelloWorld(object):
        @cherrypy.expose
        def index(self):
            return "Hello from CherryPy!"
     
    if __name__ == '__main__':
        cherrypy.quickstart(HelloWorld())

    Chaque méthode de la classe HelloWorld est exposée à l'aide du mot clé (appelé décorateur) @cherrypy.expose. Elle sera donc appelée lorsque l'URL nommée index sera entrée dans la barre d'adresse du navigateur, mais également l'URL nommée /.

    Si l'on lance ce serveur en local il affichera sur la console :

    [06/Dec/2025:15:54:57] ENGINE Listening for SIGTERM.
    [06/Dec/2025:15:54:57] ENGINE Listening for SIGHUP.
    [06/Dec/2025:15:54:57] ENGINE Listening for SIGUSR1.
    [06/Dec/2025:15:54:57] ENGINE Bus STARTING
    CherryPy Checker:
    The Application mounted at '' has an empty config.
    [06/Dec/2025:15:54:57] ENGINE Started monitor thread 'Autoreloader'.
    [06/Dec/2025:15:54:57] ENGINE Serving on http://127.0.0.1:8080
    [06/Dec/2025:15:54:57] ENGINE Bus STARTED

    A noter que le port par défaut du serveur est le 8080.

    Dans le navigateur on entrera :

    http://localhost:8080/index

    ou :

    http://localhost:8080/

    Le navigateur affichera simplement : Hello from CherryPy!

    On peut ajouter une URL supplémentaire nommée alternative :

    import cherrypy
     
    class HelloWorld(object):
        @cherrypy.expose
        def index(self):
            return "Hello from CherryPy!"
     
        @cherrypy.expose
        def alternative(self):
            return "Another Hello from CherryPy!"

    if __name__ == '__main__':
        cherrypy.quickstart(HelloWorld())

    Dans le navigateur on entrera :

    http://localhost:8080/alternative

    Le navigateur affichera alors : Another Hello from CherryPy!

    Simplissime ! Au final, pour ceux qui connaissent, comparer http.server à CherryPy revient à comparer les classes WebServer et AsyncWebServer quand on travaille sur ESP32. Quand on a goûté à CherryPy ou AsyncWebServer, il est difficile de revenir à http.server ou WebServer. On s'habitue vite au luxe.

    Comme vous le savez peut être, AsyncWebServer est capable de gérer une page HTML en utilisant une template (un modèle). En Python la gestion des templates peut être confiée à Jinja2.

    7. Générer un exécutable sous Windows

    Quand on travaille avec Python, il est assez facile de distribuer ses logiciels à des utilisateurs Linux. En effet, Python est très souvent pré-installé sur la majeure partie des distributions.

    Sous Windows, ce n'est pratiquement jamais le cas. Mais il existe un moyen : PyInstaller permet de générer un exécutable, un .exe !

    Oui, vous avez bien lu. Vous pourrez ainsi distribuer votre logiciel de manière extrêmement simple, et de plus sans divulguer vos fichiers sources. La suite ici.

    8. Conclusion

    D'après de nombreux spécialistes Python est un langage de haut niveau, simple et facile à apprendre. De plus, de nombreuses librairies sont disponibles, ce qui simplifie encore plus le développement d'applications.

    Malgré cela il a un inconvénient majeur : du fait que c'est un langage interprété, certaines erreurs de syntaxe sont détectées lors de l'exécution. Cela veut dire clairement que la phase de tests est un élément crucial dans le processus de développement. Mais cela ne veut pas dire que cela le rend moins fiable pour autant. En matière de développement, la phase de tests est en général aussi coûteuse que la phase de codage, quel que soit le langage. Il ne viendrait pas à l'idée d'un développeur professionnel de négliger ce fait.

    Tout langage nécessite un apprentissage, qui requiert en général une semaine de labeur. Pour accéder à Python il n'en sera pas autrement, mais l'investissement sera très vite rentabilisé, bien au delà de ce que peut offrir C ou C++.


    Cordialement

    Henri