Technologie

J’ai ⁣travaillé sur ‌Textual pendant plus d’un an. Voici quelques éléments que j’ai découverts (ou redécouverts) concernant les terminaux en Python et le développement ⁤logiciel ​en général.

Les terminaux sont ⁢rapides

Un émulateur ‌de terminal​ moderne est un⁣ logiciel remarquablement sophistiqué. Bien que le ⁢protocole qu’ils utilisent soit ancien, beaucoup d’entre eux sont alimentés par⁤ les ‌mêmes technologies​ graphiques que celles utilisées dans les jeux vidéo. Cependant, une animation‍ fluide n’est⁣ pas toujours garantie dans ⁣le terminal. Si vous avez déjà​ essayé d’appliquer des effets visuels‌ dans un terminal, vous avez peut-être été ‍déçu par des clignotements ⁢ou des déchirures.

Il est néanmoins possible d’obtenir une animation fluide, comme le‍ montre la vidéo suivante.​ Quels sont donc les astuces⁣ que nous utilisons ?

Il existe plusieurs ⁤facteurs qui réduisent ⁢le clignotement dans le terminal.‍ Le ⁣premier est l’émulateur de terminal que vous utilisez. Les terminaux ​modernes utilisent ⁢le rendu accéléré par le matériel⁣ et synchronisent les‍ mises à ​jour avec votre affichage pour minimiser le ‍clignotement. Cependant, d’après mon expérience, d’autres facteurs ont un impact plus important sur la ⁣réduction du clignotement que le choix de ​l’émulateur. Même sur des terminaux plus anciens,⁤ vous pouvez généralement obtenir une animation⁢ sans clignotement grâce à ⁣cette petite ⁤astuce (en réalité trois).

La première astuce consiste ‍à⁣ « écraser, ​ne ‍pas effacer ». Si ⁤vous effacez l’écran et ajoutez ensuite ⁣un nouveau contenu, vous‍ risquez de voir un cadre vide ou partiellement ‍vide pendant un ⁢bref instant. ⁢Il est bien préférable⁤ de remplacer complètement le contenu dans le terminal pour éviter‌ tout ⁣cadre vide intermédiaire.

La deuxième astuce consiste à écrire le nouveau contenu en une⁢ seule opération d’écriture sur la‍ sortie standard. Il peut être pratique ⁣de faire plusieurs appels à file.write ⁢ pour afficher une mise à jour, ​mais⁣ comme pour le cadre vide, vous risquez qu’une‍ mise à jour partielle devienne visible.

La troisième astuce consiste‌ à utiliser le​ protocole⁤ de sortie synchronisée ; une⁤ addition relativement récente​ au ‍protocole du ​terminal, mais déjà prise‌ en charge par de ⁢nombreux terminaux. ⁢vous‌ indiquez au terminal quand vous commencez et⁣ terminez un‍ cadre. Il peut alors utiliser​ ces ‌informations pour fournir des‍ mises ⁣à jour sans clignotement.

Avec ces trois astuces, vous⁤ pouvez ⁤créer des animations très fluides tant ⁣que vous pouvez fournir des mises à ⁢jour à‌ intervalles réguliers. Textual ⁣utilise 60 images par seconde comme référence. Au-delà de⁤ cela, il est probable que cela ne soit pas perceptible.

Maintenant que vous pouvez ⁤avoir une animation fluide⁢ dans le terminal, la⁣ question se pose‌ : devriez-vous le faire ⁢? Toutes les animations ne ​sont pas perçues de la ⁣même manière.⁣ Certaines peuvent ​sembler superflues. Par ‌exemple, la ‌barre latérale dans la​ vidéo⁢ qui glisse depuis la gauche de‌ l’écran. Je trouve cela astucieux, mais cela n’ajoute rien à l’expérience utilisateur. Les détracteurs des animations pourraient le considérer comme un ‍ »non souhaité », c’est pourquoi Textual disposera d’un mécanisme pour désactiver de telles animations. D’autres types d’animations‍ sont plus ‍qu’un simple effet visuel. Le défilement fluide est une animation que⁢ je trouve particulièrement ⁤utile pour garder ‌ma place dans un ​mur de texte. Toutes les animations se situent quelque part entre l’utile‍ et le superflu, ‌et je doute qu’il⁣ y ait beaucoup de personnes qui ⁤souhaitent ne pas avoir d’animation du tout.

Les DictViews sont impressionnants

Vous ​connaissez probablement les méthodes keys() et items() sur les dictionnaires Python qui renvoient respectivement un KeysView et un ItemsView. Vous ne ‌savez peut-être pas que ces objets ont des ‍interfaces très similaires⁣ à celles des ensembles. Une‌ information que j’ai redécouverte après avoir écrit ⁢inutilement une douzaine de lignes de code complexes.

Dans Textual, ⁣le processus ⁢de mise en page crée une « carte de rendu ». En gros, il s’agit d’une correspondance entre le Widget et⁤ sa ⁣position à l’écran. Dans une version antérieure, Textual effectuait un rafraîchissement inutile de tout l’écran si​ même un seul widget changeait de position. Je voulais éviter cela en comparant ​la carte de rendu avant et après.

J’ai découvert‌ que je pouvais prendre la différence symétrique​ de deux objets ​ ItemsView,⁣ ce⁤ qui me donnait les éléments qui⁢ étaient⁤ soit nouveaux, soit modifiés. C’était exactement ce dont j’avais besoin, mais réalisé au niveau C. Dans Textual, cela est utilisé pour obtenir les régions modifiées⁣ de l’écran lorsqu’une propriété CSS change, afin‌ que nous‍ puissions effectuer des mises à jour optimisées.

lru_cache est ‌rapide

Peut-être ‌pas surprenant étant donné que lru_cache est littéralement‌ conçu ‍pour⁤ accélérer votre code, mais @lru_cache est ⁢extrêmement rapide. J’ai été surpris de sa rapidité.

Si vous n’êtes pas familier ⁣avec lru_cache, il s’agit‌ d’un décorateur trouvé dans le⁢ module functools ‍ de la bibliothèque standard. Ajoutez-le à une‌ méthode et il mettra en cache la valeur⁤ de retour d’une fonction. Si ⁢vous définissez le paramètre maxsize, il veillera à​ ce que votre cache ne croisse pas indéfiniment.

En examinant‍ l’implémentation de lru_cache dans ‍les dépôts CPython, j’ai pensé‍ que je pourrais ⁢l’améliorer. Spoiler : je ne l’ai pas fait. Il s’avère que CPython utilise cette version C qui est très rapide tant pour les succès que pour les échecs de cache.

Savoir cela m’a convaincu⁢ de ‍faciliter‍ l’utilisation de @lru_cache. Il existe un certain nombre de petites ​fonctions dans Textual qui ⁣ne sont ⁤pas‍ exactement lentes, mais qui sont⁢ appelées⁤ un grand nombre‌ de ‌fois. Beaucoup‍ d’entre elles étaient hautement mises en ⁤cache, et une utilisation judicieuse de @lru_cache a permis d’obtenir⁤ un gain significatif. En général, un maxsize d’environ 1000 à 4000 était​ suffisant⁣ pour garantir que la majorité ‍des appels étaient des succès‌ de cache.

Voici un exemple de type de fonction qui a bénéficié du cache. Cette méthode calcule où deux régions rectangulaires se chevauchent. Vous pouvez voir qu’elle ne fait pas beaucoup de travail, mais elle était appelée des milliers ⁢de fois.

Un conseil lors de l’utilisation de lru_cache : vérifiez toujours ⁣vos hypothèses ⁣en⁢ inspectant cache_info(). Pour ‌un‍ cache efficace, vous devriez vous attendre à voir les ⁢ hits croître plus rapidement que les misses.

L’immuable​ est préférable

En lien avec le conseil ​précédent, j’aimerais ‍vanter les mérites des objets immuables. Python n’a pas d’objets véritablement immuables,‌ mais vous pouvez tirer parti des tuples, des NamedTuples ou des dataclasses gelées.

Il semble que ce soit une limitation arbitraire de ne pas pouvoir modifier un objet, mais cela s’avère rarement être le ​cas​ en‌ pratique. Les informaticiens soulignent que de nombreux langages sont immuables par défaut, et ce, pour de bonnes raisons.

Dans Textual, le ​code‍ qui‍ utilise des objets immuables⁤ est le‍ plus facile ‌à comprendre, à mettre en cache et à tester. Principalement parce que‍ vous pouvez ⁤écrire du⁢ code exempt d’effets secondaires. Cela est difficile à réaliser lorsque vous passez ‌des instances de classe à une fonction.

L’art Unicode est bénéfique

Certaines choses techniques sont difficiles à expliquer par des​ mots, et un diagramme⁢ créé à partir de caractères de ⁣boîte Unicode peut être extrêmement utile dans⁣ la documentation. Ce diagramme est ​extrait d’une docstring dans Textual pour une méthode qui divise une région en quatre sous-régions :

L’Utilisation ‍des Fractions en Python

Introduction aux Fractions

Bien que les commentaires de code soient ‍essentiels, l’ajout de diagrammes peut considérablement améliorer ⁢leur compréhension. Pour cela, j’utilise Monodraw, un outil exclusivement​ disponible sur MacOS, mais il existe sans doute d’autres alternatives pour les utilisateurs d’autres systèmes d’exploitation.

Précision des Fractions

Python inclut un module fractions dans sa‌ bibliothèque ⁤standard depuis Python 2.6. Pendant longtemps, je n’avais pas trouvé d’application pour ce module dans mes projets. Je pensais⁢ qu’il était⁢ principalement destiné aux mathématiciens et peu utile pour des développeurs comme moi. Cependant, j’ai réalisé à quel point il pouvait être précieux, notamment ‍pour le projet Textual.

Une fraction représente ⁣une manière⁣ alternative⁢ d’exprimer un nombre. Une fois que vous avez ​un objet Fraction, vous pouvez l’utiliser à la‌ place ‌des⁤ nombres à virgule​ flottante. ‍Mais ‍quels sont⁢ les avantages d’utiliser des⁣ fractions plutôt que des flottants ?

Les ⁢nombres à virgule flottante⁣ présentent ‍certaines limitations, un ⁣problème qui⁣ n’est​ pas ‍propre à Python. ⁤Voici ‍un exemple classique qui illustre⁤ ce problème :

>>> 0.1 + 0.1 + 0.1 == 0.3
False

Dans⁤ Textual, ces erreurs d’arrondi ⁢des flottants posaient des problèmes. Certaines mises en page nécessitaient ‌de diviser l’écran en proportions variées. Par exemple, un⁢ panneau pouvait occuper un tiers de la ⁤largeur⁤ de l’écran, tandis que les deux tiers restants étaient divisés. Des ​erreurs d’arrondi pouvaient entraîner un espace​ vide là où du ⁤contenu était attendu.

Une solution simple à ce problème était‍ de remplacer les flottants ​par des fractions.⁢ Contrairement aux flottants,‍ les fractions ne souffrent pas⁢ de ce type ‌d’erreur d’arrondi. Dans le monde⁤ des ⁤fractions, trois dixièmes⁢ s’additionnent ⁢correctement :

from fractions import Fraction as F
>>> F(1, 10) + F(1, 10) + F(1, 10) == F(3, 10)
True

Voici un ⁣exemple qui ⁢divise un nombre fixe de⁣ caractères‌ en plusieurs parties. ​Les deux fonctions accomplissent essentiellement la même tâche, mais ‌l’une utilise ‍des flottants et l’autre des fractions.

Voici le ⁤résultat du code​ ci-dessus. Notez que la version flottante (première ligne de chiffres) ⁣est courte d’un caractère :

------------------------
00011122223334444555666
000111222233344445556666

Les Défis des Emojis

Le support⁤ des emojis dans les terminaux a toujours été un ‌défi​ pour Rich, et nous avons ⁤hérité de ce problème ‌en travaillant sur Textual. C’était l’une de mes priorités lorsque Textualize a été⁣ fondé. Nous avions de grands projets, mais plus nous explorions ce sujet, plus la situation semblait complexe.

Quel ​est le problème avec les emojis ?‌ Cela découle du fait qu’un caractère‍ écrit dans ⁣le terminal‍ peut avoir une taille variable. Les ⁤caractères ⁢chinois, japonais et coréens ⁣occupent deux fois plus⁤ d’espace que ceux de l’alphabet occidental, ce qui complique le formatage, comme le centrage ou le dessin de boîtes autour du texte. Un ​formatage de base nécessite que Rich sache combien d’espace un texte occupera dans ‍le terminal.‌ Le support des caractères⁤ à double largeur complique l’utilisation de len(text) ‍ pour déterminer la largeur en terminal.

Heureusement, la base de données Unicode contient une correspondance‌ entre les caractères à ‍largeur simple et double. ​Rich (et Textual) consulte cette base‌ de données pour‌ chaque caractère qu’il imprime. Ce n’est pas une opération bon marché, mais avec un peu d’ingénierie et de mise en⁢ cache,⁣ cela devient suffisamment rapide.

Les emojis sont également présents dans ‌la base de données Unicode, donc ‌problème résolu ? Malheureusement, ⁤ce n’est pas si simple. Alors que les caractères asiatiques changent peu, ⁢les emojis évoluent constamment. Chaque nouvelle‌ version de la ⁣base de données Unicode introduit de nouveaux emojis. Si vous imprimez ces nouveaux⁢ emojis dans le terminal, les⁣ résultats peuvent être imprévisibles. Vous pouvez‍ obtenir un ⁢caractère ‌à largeur simple ou‍ double, et il⁣ se peut qu’il ne s’affiche même pas correctement.

Nous ‍avons envisagé d’inclure des informations sur chaque⁣ version de Unicode avec Rich, mais cela pose un autre problème : comment‍ détecter la version de Unicode utilisée par un émulateur de terminal‍ donné ? Il n’existe pas de méthode fiable pour cela. Il​ y a une approche heuristique où vous écrivez diverses séquences et demandez à l’émulateur de terminal⁣ la position‌ du curseur, ce ‌qui permet de faire une estimation de ‍la version de Unicode. Malheureusement, nos tests ont montré que les terminaux⁤ rendent​ encore les emojis⁤ de manière imprévisible, même⁤ si vous pensez connaître la base de données ⁢Unicode utilisée.

Pour aggraver les choses, il existe des emojis à plusieurs points de ‌code. Un ⁤point ‌de code est le numéro de référence ‍d’un glyphe Unicode ‌(image de caractère).‍ En‍ Python,⁤ vous ‍pouvez le ​consulter avec ord. Par exemple, ord("A") renvoie le⁤ point​ de code 65‌ représentant ⁣un A majuscule. Vous pourriez penser que cela est vrai pour tous les caractères, ⁤mais ce n’est ‍pas le cas. De nombreux emojis combinent plusieurs⁢ points de ⁢code pour produire un ⁢glyphe unique. Par exemple, 👨‍🦰 (homme, teint clair, cheveux ⁢roux) se compose de 4 points de code. Essayez de le ⁣copier dans le REPL Python.

Tous‍ les⁤ émulateurs ​de terminal ne rendent ‌pas ces caractères correctement. Dans certains, ils apparaissent comme 4 caractères individuels, ou 2, ou 1, avec une​ largeur simple ou ⁤double, ou parfois comme 4 « ? » caractères. Même si ⁤vous ⁤implémentez le code pour comprendre ces caractères à plusieurs points de code, vous êtes confronté au problème fondamental de ne pas savoir ​quel sera le rendu réel dans un terminal donné.

C’est un véritable casse-tête, mais en ⁤pratique, ‍ce n’est pas⁢ si mal. S’en tenir aux⁣ emojis de ⁣la version 9 de la base de données Unicode semble être fiable sur toutes les plateformes. Il est conseillé d’éviter d’utiliser des emojis plus​ récents et des caractères à plusieurs points de code, même s’ils semblent corrects​ sur votre émulateur ⁤de terminal.

Opportunités‌ chez Textualize

Nous cherchons à ⁢développer‍ un cadre ⁢TUI qui rivalisera avec‌ les navigateurs. Consultez nos offres d’emploi.

Je recrute⁢ à nouveau pour Textualize. Nous recherchons ⁢des ⁢développeurs ‌Python pour​ rejoindre notre ‍équipe.

  • Compétences techniques en Python très solides
  • Expérience web
  • Connaissance d’au moins un autre langage
  • Bonnes​ compétences en conception d’API

Les partages sont ⁢appréciés⁤ !

Show Comments (0)
Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *