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