Sam & Max http://sametmax.com Du code, du cul Wed, 30 Oct 2019 15:34:04 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.7 32490438 Le blog ferme http://sametmax.com/la-blog-ferme/ http://sametmax.com/la-blog-ferme/#comments Thu, 05 Sep 2019 08:05:31 +0000 http://sametmax.com/?p=25357 J’ai envie de passer à autre chose. Max ne s’en occupe plus depuis longtemps. Du coup je mets sametmax.com en berne. Vu le rythme de publication de 2019, ça ne fera pas une grosse différence de toute façon.

Évidemment, il restera en ligne, avec tout son contenu. 0bin.net aussi. Rappelez-vous que vous pouvez télécharger tout le blog pour le consulter hors ligne avec un petit:

wget -m -k -p -c -E http://sametmax.com

Max, pour le moment, veut bien maintenir à flot le multiboards et a même fait un peu de modération sur indexerror.net donc pour le moment, les services restent up.

J’avais déjà laissé tomber le reddit et le mastodont, mais le compte Twitter restait très actif. J’ai fait une demande de twitter pour télécharger tous les tweets pour avoir un historique. Je pense le mettre en lecture seule.

Ca a été un plaisir d’écrire les plus de 1000 articles de ce blog, avec la participation d’auteurs invités en prime, et les commentaires super sympas. Il y en a presque autant de pas publié, dont certains articles invités que je fais trainé depuis super longtemps. Désolé !

Même si Max n’a pas beaucoup écrit ces dernières années, c’est un pote en or et j’ai de la chance de l’avoir à mes côtés au quotidien. C’est vraiment hilarant d’aller à une conférence ou chez un client et d’entendre parler de nous: “tu connais sametmax ? – oui, très bon blog :) :) :)”.

Bonne pythonade à tous, et mettez un préservatif.

P.S: je rouvre les comments pour quelques jours.

]]>
http://sametmax.com/la-blog-ferme/feed/ 65 25357
Lancer correctement python et ses commandes cousines http://sametmax.com/lancer-correctement-python-et-ses-commandes-cousines/ Sat, 08 Jun 2019 18:00:02 +0000 http://sametmax.com/?p=25303 python depuis la ligne de commande, ainsi que les commandes qui lui sont liées: pip, venv, etc.]]> Si j’avais su, j’aurais écrit cet article il y a 5 ans. Je pense que tellement de monde aurait évité des heures de frustration. Mais ça prend du temps de réaliser que des choses qui vous paraissent simples sont des obstacles pour d’autres.

Mieux vaut tard que jamais j’imagine.

Dans cet article, on va voir comment lancer python depuis la ligne de commande, ainsi que les commandes qui lui sont liées: pip, venv, etc.

Sous Windows

Vous avez installé Python, c’est certain. Vous lisez votre premier tuto, qui vous dit de lancer cmd.exe, et de taper python, mais impossible de le faire marcher. Des messages du genre ‘python’ is not recognized as an internal or external command apparaissent. Ou alors le mauvais Python se lance.

Déjà, premièrement, désinstallez Python, et réinstallez-le (en utilisant l’installeur officiel si possible), mais sur la toute première fenêtre de l’installeur, vérifiez bien que la case “Add Python to PATH” (ou son équivalent français) qui est tout en bas est cochée. Sans cela, le dossier d’installation de Python ne sera pas trouvable par le shell au moment de taper la commande. Je sais, ça devrait être coché par défaut. J’ai signalé ça plusieurs fois sur la mailing-list, et ils n’en ont rien à foutre.

Le clitoris de l'installation de Python

Le clitoris de l’installation de Python

Alternativement, si vous ne voulez pas désinstaller Python, ou si vous n’utilisez pas l’installeur de Python standard, cherchez le dossier où vous avez installé python. C’est un dossier qui doit contenir python.exe et un dossier appelé Scripts. Très souvent on le trouve à C:\users\[votre nom d'utilisateur]\Local Settings\Application Data\Programs\Python\Python36. Ajoutez ce chemin dans le sys PATH, ainsi le chemin vers le sous-dossier Scripts.

Redémarrer votre console. Vous devriez au moins pouvoir taper python -v.

Maintenant, sachez que vous ne devriez pas taper la commande python.

Whaaaaaaaaaaaaat ?

Vous entends-je me hurler suavement à l’oreille.

Non. Sous Windows, vous devriez utiliser la commande py -X.Y. La commande py permet en effet de spécifier la version de Python à lancer. Par exemple py -2.7 pour lancer Python 2.7, ou py -3.6 pour lancer Python 3.6, s’ils sont installés sur la machine, bien entendu.

Pourquoi utiliser la commande py ? Et bien parce que sinon, python lancera le premier python.exe qu’il trouve. Si vous avez plusieurs versions de Python installées sur votre machine, ce qui est souvent le cas sur les machines de dev, des fois à son insu, vous allez avoir des problèmes.

En fait, quand vous lisez un tutoriel sur Python, si vous lisez quelque part “tapez python”, remplacez-le mentalement par “tapez py -X.Y”. La commande marche strictement pareil que la commande python traditionnelle. Elle prend les mêmes arguments, et lance le shell de la même façon.

Aussi, rien à voir, mais n’utilisez pas les terminaux de cmd.exe ou powershell.exe. Ils sont à chier. Prenez le temps de télécharger cmder, et si vous le pouvez, faites le cmder.exe /REGISTER ALL en admin tant que vous y êtes.

Aussi petite astuce bien pratique: si vous maintenez shift et que vous faites un clic droit dans l’explorateur de fichier ou sur le bureau, un menu contextuel différent de celui habituel s’ouvre. Il contient des actions fort utiles:

  • “Ouvrir fenêtre de commande ici”, qui vous ouvrira la console, immédiatement dans le bon dossier.
  • “Copier en tant que chemin d’accès”, qui vous permettra de mettre le chemin de n’importe quel fichier dans le presse-papier.

Sous Unix (Max, Linux, etc)

N’utilisez pas la commande python, mais utilisez une commande suffixée pythonX.Y. Par exemple, pour lancer Python 2.7, tapez python2.7, ou pour lancer Python 3.6, tapez python3.6.

En fait, quand vous lisez un tutoriel sur Python, si vous lisez quelque part “tapez python”, remplacez-le mentalement par “tapez pythonX.Y”. La commande marche strictement pareil que la commande python traditionnelle. Elle prend les mêmes arguments, et lance le shell de la même façon, mais comme on a souvent plusieurs versions de Python installées sur une machine (sans parfois même sans rendre compte), c’est important de le faire.

Pour les linuxiens seulement

Pip et virtualenv sont fournis quand on installe Python sous Mac et Windows, mais souvent pas sous Linux ! Assurez-vous de toujours les installer avec votre gestionnaire de paquet, et ce pour chaque version de Python que vous avez.

Exemple: yum install python3.6-pip ou apt install python3.6-venv.

Tous OS confondus

Quand vous utilisez une commande écrite en Python, mettez python -m devant. C’est un cheat code.

N’appelez-pas pip, mais python -m pip.

N’appelez-pas venv, mais python -m venv.

N’appelez-pas black, mais python -m black.

Si vous lisez dans un tutoriel “tapez pip install”, remplacez-le mentalement par “tapez python -m pip install”.

-m ne marche pas avec toutes les commandes (python -m jupyter console a un bug, snif) car cela suppose que le développeur de la commande y a pensé, mais c’est le cas de la plupart des outils modernes.

Or -m va vous éviter tout un tas de problème de PATH, de droits et de version de Python.

Donc, si on combine tous les conseils, n’utilisez pas pip, mais py -3.6 -m pip ou python3.6 -m pip.

Je sais, c’est plus long a taper, mais ça va bien vous aider.

pip install

Certains outils doivent s’installer en global. On voit souvent des conseils comme faire un sudo pip install ou un sudo easy_install. C’est mal. Ceci va pourrir les paquets système de Python, et peut avoir des conséquences indésirables. Notez que parfois, rien ne marche, et je le fais quand même du coup.

Mais la plupart du temps, ce qu’il vous faut, c’est --user, suivi de -m. Par exemple, ne faites pas:

 
sudo pip install black 
black mon_fichier.py 

Mais faites:

 
python3.6 -m pip install black --user 
python3.6 -m black mon_fichier.py 

Notez le --user, ainsi que les deux usages de -m.

Ceci va installer black localement, pas au niveau du système. On s’assure qu’on n’utilise bien Python3.6, à l’installation et à l’usage de black. Et comme on n’utilise -m, on a pas à se demander si la commande black est bien sur le PATH (pas besoin de trifouiller son .bashrc ou le sys PATH de Windows.)

Mais c’est trop chiant !

Absolulement, c’est aussi pour cela qu’on utilise des environnements virtuels.

Utilisez des virtualenvs. Abusez-en. Un virtualenv par projet. Un autre pour les tests rapidos. Un pour maman, un pour papa, et un pour le fun. Ils ne coûtent rien que quelques Mo sur votre disque dur, donc lâchez-vous.

Car dans un virtualenv, non seulement vous êtes isolés des autres installations de Python, mais en plus… tous les conseils ci-dessus ne sont plus nécessaires !

Vous pouvez taper juste python et juste pip, plus de py, plus de suffixes, plus de -m et --user. Joie !

Moralité: les rares fois où vous êtes hors virtualenv, suivez les conseils des autres parties de l’article (py -3.6 -m pip install --user ou python3.6 -m pip install --user). Et des que vous le pouvez, pouf, virtualenv, et tout va pour le mieux.

]]>
25303
Stack Python en 2019 http://sametmax.com/stack-python-en-2019/ http://sametmax.com/stack-python-en-2019/#comments Sun, 03 Feb 2019 15:10:21 +0000 http://sametmax.com/?p=25193 Débuter avec Python en 2019, je me suis dis qu'il serait bon d'en rajouter une couche. Après toutes ces années, qu'est-ce que j'utilise pour mes projets Python ?]]> Suite au très bon billet Débuter avec Python en 2019, je me suis dit qu’il serait bon d’en rajouter une couche.

Après toutes ces années, qu’est-ce que j’utilise pour mes projets Python ?

D’abord, Python 3.6, partout

Pas la 3.5. Pas la 3.7.

La raison est que la 3.6 est un millésime exceptionnel, dans laquelle culminent des années de fixes et goodies. Malgré celà, sortie fin 2016, elle est facile à installer: ça prend quelques minutes sur même une centos 7 via les EPEL, ou sur une ubuntu 16.04 en utilisant le ppa deadsnake.

La 3.7 n’est non seulement pas aussi aisée à déployer, mais elle ne contient pas encore autant de correctifs, et surtout fige les mots clés async / await, créant des surprises. J’admets volontiers que breakpoint(), les dataclasses et asyncio.run() sont très tentants, mais je peux vivre sans.

Donc Python 3.6.

(P.S: django et numpy droppent le support de la 3.4)

Pour déployer, pex et nuikta

Quand j’ai scripts rapides mais pleins de dépendances , fini les virtualenvs et pip install en prod. Je package tout avec pex. Un fichier .pex est un format conçu par twitter l’équivalent d’un .war, que la présentation de Brian Wickman expliquera mieux que moi.

Mais en gros, en très gros, c’est un zip qui contient tout le virtualenv. On fait python mon_projet.pex et ça lance tout. Pas besoin de déploiement compliqué. scp, et c’est prêt. Je ne le recommande pas pour un gros projet web par contre. Mais pex rend le scripting Python merveilleux, et presque trop facile: plus besoin de s’interdire une dépendance par peur que le serveur ne l’a pas, ou par flemme. Du coup, même le script le plus simple à la puissance de feu de tout pypi, et ça m’a permis de faire des one-shots très complexes en deux coups de cuillères à pot.

Quand je dois livrer un programme chez un client qui ne soit pas Web, je compile tout avec nuitka. Je me fends même parfois d’un installeur nsis au besoin. Demander à autrui d’installer la VM est source de beaucoup de problèmes, et Python est un détail d’implémentation pour beaucoup d’utilisateurs de toute façon.

Gestion de projet: pew et setup.cfg

J’aime poetry, mais la compatibilité avec setuptools est importante à mes yeux. Tout l’écosystème supporte setuptools, tout est bien testé, c’est robuste et sans surprise. J’attendrais que poetry soit stable, testé, bien intégré et surporté. Évidemment, il faudra que pyproject.toml soit suffisamment mature également, et vu l’usage de sections custo pour tous les outils qui l’utilisent, on en est encore loin.

pew est très basique, mais il ne s’occupe que du virtualenv, laissant la gestion de mon projet à mes soins. Il est rapide, et sans chichi, de plus personne dans mon équipe n’a besoin de savoir que je l’utilise.

Après quelques essais, j’ai laissé tomber pipenv, qui a trop de problèmes, et dont l’auteur met des années avant d’entendre raison sur des choses essentielles, sur lesquelles il revient par ailleurs sans mine de repenti. Ça n’en retire rien à Kenneth Reth le mérite des ses travaux, mais j’ai des deadlines.

Outillage

À moins d’avoir été sourd et aveugle, vous avez du noter un certain engouement de la communauté pour black, que je vous ai déjà dis avoir adopté.

Les conséquences sont multiples sur la stack: j’ai viré flake8, et j’ai allégé de nombreuses règles ma configuration pylint, qui est du coup mon seul linter. Faudrait que je fasse un article dessus d’ailleurs.

Mypy étant maintenant stable et utilisable, je l’active par défaut. Ça ne veut pas dire que j’annote tout mon code. Parfois je n’annote rien. Parfois juste quelques fonctions. L’énorme avantage de mypy réside dans le fait que son utilisation est parfaitement progressive, et s’adapte à l’engagement et l’effort que vous voulez y mettre.date mypy est une des rares choses que j’upgrade à tout bout de champ, car chaque update amène une vraie qualité de vie en plus.

J’intègre tout ça dans mon éditeur, et en l’occurence VSCode rend l’opération très facile.

Pour le lanceur de tests, je reste sur pytest, pour des raisons déjà exposées. Honnêtement je ne connais pas de bonnes raisons de ne pas utiliser pytest. J’utilise faker pour générer des fausses données de test. Je n’arrive toujours pas à utiliser hypothesis. J’essaye, mais je n’arrive jamais à l’appliquer sur autre chose qu’un exemple joujou. J’espère y arriver un jour, car je suis certain que c’est excellent.

Pour lancer tout ce bordel, j’utilise tox, mais je ne l’utilise pas sur tous mes projets: seulement les gros avec certaines exigences.

Je n’ai pas de template de projet type. J’ai beaucoup lorgné depuis des années du côté de cookiecutter, je n’arrive pas à me motiver à l’utiliser sérieusement.

Enfin, j’installe toujours jupyter pour tester vite fait mon code, bien que j’utilise plus la console que le notebook. Et sphinx pour la doc.

Frameworks Web

Django (souvent avec django-rest-framework).

J’ai parfois des clients qui exigent flask, et donc je fais du flask. C’est toujours à regret.

Il n’a rien que flask me permette que je ne puisse faire avec Django, mais il y a une tonne de trucs à réimplementer à la main à chaque fois. À documenter. À tester. Tout ça pour changer de projet flask, et tomber sur un nouveau loustic qui a fait les trucs à sa sauce et tout recommencer.

Les projets flask ne sont bien faits que par ceux qui savent déjà très bien mener un projet Web, ce qui n’est pas la majorité des gens qui l’utilisent: en effet, il attire les utilisateurs par la simplificité de son API, et leur donne l’illusion d’être à la hauteur.

flask reste un excellent produit pour l’éducation, ou pour un petit projet vite fait, ce pour quoi je le choisis avec plaisir. Mais la majorité des projets flasks sur lesquels j’ai travaillé n’ont guère le niveau de qualité qu’on rencontre en moyenne dans les projets Python. C’est qu’on s’habitue, à force.

J’essaye aussi d’aimer SQLALchemy, dont je reconnais la flexibilité et la puissance. Mais son ergonomie est pénible, et la gestion des sessions suffisamment tortueuse pour se tirer une balle dans le pied si on cligne trop des yeux. Si j’ai des problèmes de perfs, je fais du SQL à la main de toute façon et j’utilise du cache en masse. Je reste donc sur SQLA seulement si flask, ou hors Web. Et encore, des fois je peeweese.

L’ORM de Django est une aberration en bien des points, sauf un seul. Il est éminemment pratique. Et je sais jusqu’où je peux le pousser: loin, très loin. Max va se gausser en lisant ces lignes, mais je me lasse de la beauté du code et de la pureté (ta gueule mec, arrête sourire, je te vois).

Bref, Django pour les seniors. Django pour les juniors, même si ça prendra plus de temps que flask, mais au moins le framework leur évitera de faire de la merde comme au temps de PHP à la main.

Question async, j’utilise aiohttp mais aussi fais du asyncio à là main, en faisant bien attention aux goto.

Non, je n’utilise pas les trucs du genre sanic, growler, vibora, quart, etc. Si ils sont toujours activement développés dans 3 ans, on en reparle.

Bonne nouvelle ceci dit, la doc d’asyncio est enfin potable, si vous voulez vous y mettre. Mais ça mérite quand même un article.

Ceci dit, les occasions de faire de l’asyncio sont rares. REST reste (hu hu) quand même l’option reine, HTTP2 peut se déployer via proxy et les threads assurent le plus souvent des perfs suffisantes. Pour être parfaitement honnête, j’utilise plus asyncio pour des scripts, daemon, et autres tâches de fond :)

En parlant de tâche de fond, je reste sur du celery, surtout depuis que je sais qu’on peut l’utiliser avec presque zero config. C’est le moins pire des systèmes. De toute façon j’ai presque toujours un redis sous la main, c’est bien trop facile et pratique pour s’en passer.

J’ai pas encore mis en prod django channels, ou aWSGI, mais je ne suis pas du tout convaincu par ces solutions, donc j’attends de voir.

J’ai eu l’occasion d’utiliser crossbar un peu plus. L’outils est toujours excellent, mais l’API a changé sans pour autant s’améliorer. Ils se recroquevillent dans l’illusion qu’un produit spécialisé dans l’IoT est une bonne stratégie, mais c’est une voie de garage à mes yeux. Le vrai potentiel est dans le Web. Je me fais à l’idée que le projet n’attendra jamais son potentiel tant que personne n’écrit une surcouche, et que ce ne sera pas tavendo qui le fera.

Libs

Les libs changent constamment, partant et venant selon la nécessité des projets. Au mieux puis-je vous dire ce que j’irais chercher si j’avais cette problématique.

Dates: pendulum.

Validation de données: marshmallow.

cli: click. Ça me fait mal de le dire, car je suis pas fan du style des APIs d’Armin (l’objet request global de flask, sérieux…), mais quand il fait un truc, on est sûr que ça marche. J’ai eu trop de limitations avec les alternatives.

Gestion du pognon: money.

Lib graphique: wxPython. Je fais du QT sur demande client, mais c’est trop gros pour des projets de moyenne taille.

Encoding: toujours chardet et unidecode.

Manipulation d’images: pillow.

Calculs numériques: numpy. À mon niveau je n’ai jamais besoin de scipy ou pandas.

Pour le parsing de conf je fais de plus en plus de toml, mais j’ai pytoml est pas terrible. Je vous ferai un retour sur contoml qui, si tout se passe bien, devrait être mon futur default.

Pour convertir du code Python 2 vers Python 3, python future et backport.

Pour faire du templating, jinja2.

Pour l’internationalisation, babel.

Hors de Python

Git. Vue. Webpack. Ubuntu.

J’évite docker comma la peste, même si certains clients le veulent à tout prix. Je fais du react, généralement sous la torture.

Pas grand-chose à dire de plus.

Ah si, j’ai laissé tombé zsh et fish pour revenir à bash. Le ROI me convient pas.

]]>
http://sametmax.com/stack-python-en-2019/feed/ 1 25193
Le type bytes n’est pas du texte http://sametmax.com/le-type-bytes-nest-pas-du-texte/ Fri, 11 Jan 2019 11:30:10 +0000 http://sametmax.com/?p=25125 je craque. Mais je me soigne, globalement j'ai récupéré plein de temps, et ça se voit sur mon quotidien. Et ce craquage, et bien il est cette fois dû à une totale mécompréhension des types de texte en Python 3.]]> J’ai beau essayer très fort de ne pas répondre en ligne, des fois je craque. Mais je me soigne, globalement j’ai récupéré plein de temps, et ça se voit sur mon quotidien.

Et ce craquage, et bien il est cette fois dû à une totale mécompréhension des types de texte en Python 3.

Mais c’est bien normal: Python 3 ne gère pas le texte de la même manière que la grande majorité des langages de programmation, justement à cause de la débâcle qu’on a eue en Python 2. Du coup, de nombreux programmeurs arrivent avec leur expérience d’ailleurs, et tentent de l’appliquer tel un utilisateur de SVN migrant sur git. En surface ça semble coller, malheuseuement à l’usage, ça fait faire des erreurs.

Donc un peu d’explications.

En informatique, tout est une histoire de convention. On dit que tel mot clé a tel effet. Que tel nom suppose telle chose. Que tel code de retour implique telle erreur. Que tel schéma XML représente tel type de document.

Essentiellement, tout cela est arbitraire: des gens ont décidé qu’il en serait ainsi. Impossible de deviner que ce que fait yield ou with si vous n’avez pas d’expérience similaire avant. Impossible de savoir que le code 0 en bash ou 200 en HTTP signifie tout va bien sans qu’on vous transmette l’information, ou faire de nombreux tests.

Quand je dis arbitrairement, évidemment je ne veux pas dire complètement sans raison. Il y a des raisons techniques, politiques, économiques, et parfois esthétiques à ces conventions. Cela n’en retire en rien l’aspect parfaitement artificiel de ces choix.

La convention la plus omniprésente, et pourtant aujourd’hui la plus masquée dans un monde où on utilise massivement des langages de haut niveau comme Javascript, Ruby, PHP et Python, est celle de l’organisation des octets.

Musique !

…je vois même plus le code : tout ce que je vois, c’est des blondes, des brunes, des rousses.

Tout ce qui passe par nos ordinateurs n’est qu’une suite de zéros et de uns, que nous avons groupés par paquets de 8:

Seulement la grande révélation, le “aaaaaaahhhhh okayyyyyyy” qui arrive un jour dans toute vie de dev, c’est que ces paquets de 8 ne veulent rien dire. Rien. C’est nous qui avons décidé, arbitrairement encore une fois, de leur signification.

Vous voyez ce moment dans les films et séries où un personnage arrive à “lire du binaire” ?

Evidement, "c'est une representation binaire ASCII de coordonnées WGS 84 Web Mercator" est plus dur à caser dans un dialogue

Evidement, “c’est une representation binaire ASCII de coordonnées WGS 84 Web Mercator” est plus dur à caser dans un dialogue

C’est de l’enculage de dauphin.

Le binaire n’est pas un langage, pas plus que les lettres “abcdefghijklmnopqrstuvwxyz”. Vous pouvez utiliser ces lettres pour représenter certains mots italiens, français, anglais, un nom propre (sans langue), le label d’un immeuble (sans langue encore) ou un chiffre latin.

Que veut dire “les gosses” ? Pour la même combinaisons de lettres, cela signifie “les enfants” avec la convention française européenne, et “les couilles” avec la convention québéquoise.

Pour le binaire c’est pareil, ce que veut dire un octet dépend de la convention que vous avez choisie.

Par exemple, que signifie cette suite d’octets ?

1100001 1100010 1100011 1100100

Bah rien. Mais on peut lui donner un sens en lui appliquant une convention.

Je peux lui appliquer la convention ASCII, et donc supposer que c’est un texte dans un certain format. Voici ce que ça donne en Python:

     
>>> data = bytearray([0b1100001, 0b1100010, 0b1100011, 0b1100100])     
>>> print(data.decode('ascii'))     
abcd 
Les processeurs modernes ne comprenent pas nativement l'american apparel

Les processeurs modernes ne comprenent pas nativement l’american apparel

Ou je peux lui appliquer une autre convention, et decider de lire ces octets comme si ils étaient le dump d’une structure C. Interprettons en Python ces octets comme un entier non signé en big-endian:

     
>>> data = bytearray([0b1100001, 0b1100010, 0b1100011, 0b1100100])     
>>> import struct     
>>> struct.unpack('>I', data)     
(1633837924,)

Même suite de bits, mais selon la convention choisie, elle veut dire les lettres “abcd” ou le nombre “1633837924”. Et oui, comme il n’y a pas une infinité de combinaisons de 0 et de 1 qui tiennent dans un espace mémoire limité, différentes conventions vont utiliser les mêmes octets mais décider que ça veut dire quelque chose de différent.

En fait, même des conventions pour le même type usage ne veulent pas forcément dire la même chose. Par exemple, prenez l’octet:

11101001

Un octet somme toute sympathique, de bonne famille. Il ne paie pas de mine, mais c’est un membre utile de la société.

Et maintenant, quelqu’un vous donne un indice, il vous dit que cet octet représente… du texte.

Super !

Oui, mais du texte avec quelle convention ? Car les pays du monde entier ont créé leur propre convention pour représenter du texte.

Avec la convention “latin-1”, utilisé par 0.7% de tous les sites Web du monde ?

 
>>> bytearray([0b11101001]).decode('latin-1') 
'é' 

Avec la convention “cp850”, utilisé par la console DOS ?

 
>>> bytearray([0b11101001]).decode('cp850')
'Ú'

Vous voulez rire ? Le premier à remplacé presque partout le second parce qu’ils contiennent les mêmes lettres. Elles ne sont juste pas représentées par la même combinaison d’octets.

Et cet octet, que veut-il dire avec la convention “utf8”, qui est aujourd’hui le standard international recommandé pour représenter du texte ?

 
>>> bytearray([0b11101001]).decode('utf8')
Traceback (most recent call last):
File "", line 1, in 
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 0: unexpected end of data 

Il n’a pas de correspondance. Cet octet n’est pas de l’utf8 valide.

Si vous voulez représenter ces lettres en utf8, il faut utiliser une convention différente, en utilisant non pas un seul octet, mais une séquence d’octets:

 
>>> list(map(bin, 'é'.encode('utf8')))
['0b11000011', '0b10101001']
>>> list(map(bin, 'Ú'.encode('utf8')))
['0b11000011', '0b10011010']

Vous pourriez croire que puisque le texte est particulièrement compliqué, c’est normal d’avoir des conventions qui divergent. Mais non, c’est juste la nature des conventions. Puisqu’elles sont arbitraires, l’une n’est pas plus “la vérité” qu’une autre. On retrouve la même chose avec les nombres:

>>> struct.unpack("h", bytearray([0b11101001, 0b11101001]))
(-5655,)
>>> struct.unpack("H", bytearray([0b11101001, 0b11101001])) 
(59881,)

La même suite d’octets peut représenter deux nombres totalement différents, selon que je décide de les lire comme des “short”, ou des “unsigned short”.

Et l’inverse est aussi vrai.

Ben oui, si quelque chose peut être interprété de plusieurs façons, on a aussi le fait que deux représentations différentes peuvent être interprétées … pour aboutir au même résultat.

Par exemple, le nombre des doigts de ma main peut être représenté de plein de façons différentes:

  • décimal: 5
  • français écrit: cinq
  • chiffre latin: V
  • anglais écrit: five
  • espagnol écrit: cinco
  • base deux: 101
  • structure C d’un signed short en little-endian avec Python: bytearray([0b101, 0b0])

Que de manières différentes, pour le même concept ! En plus, il y a confusion possible: V est une lettre également. cinq, five et cinco utilisent le même alphabet, mais pas les mêmes symboles spécifiques, pour représenter la même chose. Et le plus confusionant, 101 est une représentation binaire, mais bytearray([0b101, 0b0]) aussi.

Bref, voilà toute la complexité de la différence entre la donnée, un concept abstrait qui n’existe pas, et sa représentation, une convention humaine concrète qui nous permet de communiquer entre nous.

Donc, pour lire “du binaire”, ou faire n’importe quoi en informatique, il faut connaitre la convention utilisée. Mais pas juste en informatique: pour lire le journal, il faut connaitre la convention des symboles imprimés sur les pages, pour conduire sans se faire tuer, il faut connaitre la convention des panneaux, et pour parler, il faut connaitre la convention de la compression des molécules d’air émise par l’appareil buccal et respiratoire d’un individu qui vient rencontrer votre système auditif.

Vous êtes un être très conventionnel au fond.

Évidemment on trouve la même chose en Python. Par exemple vous pouvez utiliser plusieurs conventions pour demander à Python de créer le même nombre en mémoire:

>>> 245 # base 10
245
>>> 0xF5 # hexadecimal
245
>>> 0b11110101 # binaire
245
>>> 245 == 0xF5 == 0b11110101
True     
>>> type(245)     
     
>>> type(0xF5)     
     
>>> type(0b11110101)     
 

Inversement, "1" et 1 paraissent similaire, mais ils ont différents buts. Le premier est un outil destiné à l’affichage, qui matérialise le caractère représentant le chiffre arabe après le zéro. Il est stocké en interne avec une séquence d’octets similaire à:

>>> bin(ord("1"))
'0b110001'

Tandis que que le second est un outil fait pour faire des calculs avec la plus petite valeur positive entière non nulle. Il est stocké en interne avec une séquence d’octets similaire à:

>>> list(map(bin, struct.pack('l', 1)))
['0b1', '0b0', '0b0', '0b0', '0b0', '0b0', '0b0', '0b0']

Je simplifie bien entendu, en vérité la representation interne des nombres et du texte en Python est plus complexe que cela, et dépend de l’implémentation choisie, du type de processeur, de la taille de la donnée et de votre configuration.

Retour sur le type bytes

J’ai soigneusement évité d’utiliser le type bytes durant cette démonstration, le remplaçant techniquement inutilement (mais pédagogiquement brillamment, car je suis génial) par bytearray.

En effet, toute cette leçon est là pour arriver à la conclusion que bytes ne représente pas du texte, mais si je vous avais montré tout ça avec lui, voilà qui vous aurait interloqué:

     
>>> bytes([0b1100001, 0b1100010, 0b1100011, 0b1100100])     
b'abcd' 

“Heu, mais c’est du texte !” me dirait alors un lecteur ayant diagonalisé l’article.

Mais bien entendu que non.

bytes ne présente pas du texte, c’est une structure de données dont le but est de permettre de manipuler une séquence d’octets ordonnée, et ce manuellement. N’importe laquelle.

Or, il se trouve que beaucoup de langages de programmation représentent le texte comme un array d’octets, et y attachent quelques opérations de manipulation. C’est le cas du C, ou de Python 2 par exemple. Les gens ayant eu cette expérience pensent donc que b'abcd' représente du texte, allant parfois jusqu’à aller lui donner l’appellation de “byte string”.

Il n’existe rien de tel en Python 3.

En Python 3, vous avez deux types pour manipuler des séquences d’octets: bytes et bytearray. Ils sont équivalents, à ceci près que bytes est non mutable (non modifiable) alors que bytearray est mutable (modifiable).

Ces types peuvent contenir n’importe quels octets, et nous avons vu ensemble qu’une même séquence d’octets pouvait être interprétée différemment selon la convention choisie pour la lire. Évidemment il est préférable de la lire avec la même convention qui a été utilisée pour la produire, sans quoi on ne comprendra pas ce que le producteur de la donnée à voulu dire.

Sauf que…

Beaucoup d’outils en informatique utilisent les conventions ASCII et hexadécimale pour symboliser les valeurs des octets. Si vous lancez Wireshark pour regarder les paquets d’un protocole réseau ou si vous ouvrez un PNG avec xxd, on va vous représenter le contenu avec un mélange de ces conventions.

Pour des raisons pratiques, Python fait donc la même chose, et permet ainsi de visualiser (ou produire) le type bytes à l’aide d’une notation ASCII:

    
>>> print(b'abcd'.decode('ascii'))     
abcd     
>>> struct.unpack('>I', b'abcd')     
(1633837924,)

Ou d’une notation héxa (ironiquement, l’héxa est representé par une combinaison de caractères ASCII \o/) si les valeurs ne tiennent pas dans la table ASCII:

     
>>> "é".encode('utf8')  # hexa C3 A9   
b'\xc3\xa9'     
>>> struct.unpack('h', b'\xc3\xa9')    
(-22077,)

Donc bytes, bien qu’il puisse contenir des octets interprétables comme du texte, n’est pas particulièrement fait pour manipuler du texte. Il peut contenir n’importe quoi. Mais pour des raisons pratiques, sa représentation dans le terminal est faite avec une convention familière. Après tout, il faut bien l’écrire en quelque chose pour l’affiquer à l’écran.

Si on veut manipuler du texte en Python 3, il faut utiliser le type str, qui est l’outil spécialisé dans la representation et la manipulation textuelle. Si vous savez qu’un type bytes contient des octets qui representent du texte, alors utilisez la méthode décode() avec la bonne convention (appelée “charset”), pour récupérer un str:

     
>>> print(b'P\xc3\xa8re No\xc3\xabl'.decode('utf8'))
Père Noël 

On a un très bon article sur l’encoding en Python sur le blog, d’ailleurs.

Toute cela n’était bien entendu pas vrai en Python 2. En Python 2, le type str était un array d’octets, rendant tout cela bien confus, et amenant à plein d’erreurs. L’introduction lors de la version 2.0 de l’objet unicode pour pallier le problème, bien que très utile, n’a fait que rajouter à l’incomprehension des nouveaux venus.

Or le monde extérieur, lui, n’a pas d’abstraction pour le texte. Faire des abstractions, c’est le rôle du langage de programmation. Si vous écrivez dans un terminal, ou lisez depuis un terminal, un nom de fichier, le contenu d’une base de données, une requête AJAX, etc., ce sont évidemment des octets qui sont échangés, et il vous faut la bonne convention pour faire partie de la discussion.

Le type bas niveau bytes est un outil qui sert donc à communiquer avec le monde extérieur, tandis que les types haut niveau (str, int, list, etc.) sont des outils qui font l’abstraction de ces conventions, pour vous permettre de manipuler confortablement un concept général (du texte, un nombre, une collection ordonnée) à l’interieur des murs de votre programme.

]]>
25125
Ecouteurs bluetooth http://sametmax.com/ecouteurs-bluetooth/ Thu, 10 Jan 2019 10:31:17 +0000 http://sametmax.com/?p=25112 Je mets une fortune dans mes casques, que j’abime et perds en plus très souvent. Or, j’ai toujours eu une relation mitigée avec les écouteurs Bluetooth (et le BT en général d’ailleurs…), et je suis assez froissé par la tendance à supprimer les jacks.

Le fil d’un ami m’a fait pourtant tester les airpods, et j’ai été surpris par la qualité du son et la facilité d’usage. J’ai cherché un concurrent.

Après enquête, j’ai tenté les Jabra Elite 65T. Après des mois d’usage, voici ce que je peux en dire…

Les plus:

  • Le son est excellent, particulièrement pour la taille sur du sans-fil, y compris sur du
    classique, et les basses ne sont pas sur amplifiées comme c’est souvent le cas pour plaire à la masse;
  • C’est confortable. Je les porte des heures;
  • 5h d’autonomie + 5 de secours avec le boitier. Je suis arrivé au bout seulement 2 fois
    malgré des mois d’usage, donc pas un problème. Mais c’est vrai que les airpods font le double.
  • La synchro Bluetooth est parfaite avec portable récent (donc le messie bt5): rapide,
    sans galère. Quand on ressort les écouteurs du boitier, le pairing est automatique et transparent.
  • Le boitier de transport / chargeur / batterie de secours fonctionne à merveille.
  • L’attache dans l’oreille est excellente. On peut se faire secouer, courir, faire du
    yoga… Ça bouge pas.
  • On peut écouter avec une seule oreille, mais seulement la droite.
  • Les appels sont limpides, on a du mal à croire que ce petit micro marche si bien.
  • Après la perte inéluctable des deux écouteurs, j’ai fait une demande au SAV. 40 Euros,
    et reçu en 2 jours. Beau. Resyncro instantanée une fois placée dans le boitier.
  • De vrais boutons qui permettent de contrôler pleinement la lecture, et pas juste 3
    tapotements limités et difficiles en mouvement.
  • 3 tailles embout. Et choisir les bons a un gros impact sur le confort. Les
    intra-auriculaires à taille unique, ça m’a toujours laissé perplexe.
  • Bonne isolation du son extérieur.
  • Retirer un écouteur coupe la musique.
  • Zéro latence. J’ai eu des écouteurs BT moins cher, et regarder des films avec était un
    enfer. Là c’est fantastique, avec VLC ou Netflix dans le train, on se fait plaisir.

Les moins:

  • 180 euros à l’achat. Bam.
  • Le boitier est trop gros. Contrairement à celui des airpords, le mettre dans la poche n’est
    pas aussi discret qu’une boite d’allumettes.
  • Il y a un mode pass-through si vous êtes en vélo et que l’isolation sonore est dangereuse,
    mais il est pas ouf. Mieux vaut reste sur une écoute d’une oreille.
  • Certains comportements sont contre-intuitifs: retirer l’écouteur gauche coupe la musique,
    mais en fait on peut la relancer sur un seul écouteur en pressant le bouton principal. On peut aussi désactiver ce comportement via une app pour partager le son avec son voisin. Parfois l’écouteur gauche se met en veille quand il reste trop longtemps éloigné du droit, alors alors il faut une pression longue dessus pour le réveiller: le web est plein de gens qui croient que leur écouteur ne marche plus. Rien n’indique ces manipes sans un bon vieux RTFM.

Finalement, ces petits joujoux m’ont réconcilié avec le sans-fil, et je n’ai presque plus touché à mes écouteurs ou mes baffles Bose. Certes, je n’écoute pas du flac, donc même si la qualité est bonne pour du mp3, j’imagine qu’un audiophile confirmé y trouvera à redire.

Ok, ok. Dans ces conditions, virez le jack… quand l’USB-C sera supporté partout.

]]>
25112
Le don du mois: Libre Office http://sametmax.com/le-don-du-mois-libre-office/ Tue, 08 Jan 2019 08:54:56 +0000 http://sametmax.com/?p=25104 Microsoft Office est un bon produit. Voilà, je l’ai dit. Je peux ne pas apprécier Microsoft en tant qu’entreprise, reprocher de nombreux défauts à ses bébés, mais être objectif sur certains points: Excel, malgré ses bugs, reste la meilleure expérience de tableur au monde, .Net est une technologie très propre, et VSCode est un superbe éditeur que j’ai par ailleurs adopté.

Seulement voilà, je n’ai nullement l’intention de soutenir des formats propriétaires, l’obsolescence programmée, et l’abus de position dominante. Au contraire, je préfère soutenir le logiciel libre, les standards ouverts et le partage.

Et c’est là que Libre Office entre en jeu.

Je l’utilise donc au quotidien, pour faire des rapports, des lettres, des calculs, etc. Bien que je sois très à l’aise avec ma machine, j’utilise des fonctions relativement peu avancées, et ce logiciel comble donc tous mes besoins.

Malheureusement, Libre Office souffre de gros problèmes d’ergonomie, et pire encore, de fiabilité. Sa stabilité est douteuse, et en plus de la frustration liée au plantage, il m’arrive de perdre du travail, chose qui me rend furieux.

Je comprendrais donc parfaitement que quelqu’un choisisse de ne PAS utiliser Libre Office, pour toutes ces raisons.

Personnellement, j’ai la politique inverse, et j’utilise Libre Office pour les soutenir, et je vais donc reporter les bugs et autres problèmes quand j’en ai le temps. J’ai peu de temps en ce moment, donc je choisis de contribuer par don, ce qui j’espère aidera les auteurs à dépasser ces hics. Un jour peut-être.

Un peu comme Firefox que j’ai gardé pendant des années alors qu’il était inférieur à Chrome, jusqu’à ce que, soudain, il redevienne non seulement aussi rapide, mais rajoute des fonctionalités inédites comme les tab containers que n’a pas la concurrence.

Et voici donc un don de 50 euros pour cet excellent projet, qui derrière ces soucis, permet quand même à peu de frais de faire des choses absolument incroyables, le tout dans le respect de la communauté humaine.

Bien entendu, je vous invite à faire de même.

]]>
25104
Vive setup.cfg (et mort à pyproject.toml) ! http://sametmax.com/vive-setup-cfg-et-mort-a-pyproject-toml/ Thu, 06 Dec 2018 10:07:48 +0000 http://sametmax.com/?p=25060 Après un long débat sur hackernews, qui n’est que le reflet de toutes les conversations que j’ai déjà eues à ce sujet sur twitter, github, et divers mailling lists, il est grand temps de faire un article. Urgent même.

Est-ce que vous savez quel chemin de croix on a vécu avec le packaging Python durant ces 15 dernières années ?

D’abord on a distutils, setuptools, distribute, and distribute2 qui ont tous été à un moment les “standards” recommandés pour packager une lib. Ensuite on a eu l’époque des eggs, exe, et autres trucs que easy_install allait chercher n’importe où dans la nature en suivant aveuglément des liens sur PyPi. Sans compter les machins qu’il fallait compiler à tout bout de champ. Et puis rien n’était chiffré au download, pip n’était pas packagé avec Python, il crevait sur des erreurs stupides type encodage mal géré…

À ça se rajoute que virtualenv était un truc à part, avec plein de concurrents, et linkait les packages système par défaut. Sans oublier qu’on avait pas Python -m.

Bref, le packaging Python, ça a été vraiment la merde. Avec en plus une doc de merde.

Aujourd’hui, le standard wheel a énormément amélioré la donne. On a des tutos corrects (ex: notre tuto sur comment créer son package avec setup.py). ensurepip fait qu’on a une version récente du truc presque partout.

En gros, notre situation est stable, saine. Améliorable de bien des façons, certes, mais un bon socle sur lequel s’appuyer.

Arrive setup.cfg

En 2016, l’équipe de setuptools, la lib utilisée par à peu près tout le monde pour créer des packages en Python aujourd’hui, a créé le format setup.cfg, un fichier INI dont le but est de remplacer setup.py.

Le problème de setup.py c’est que c’est du code Python exécutable, et en plus dépendant de la lib setuptools. Cela empêche non seulement l’interfaçage avec des outils externes, mais aussi freine l’émergence de nouveaux outils.

En effet, il y a un désir de continuer à améliorer la situation du packaging en Python, comme on peut le voir avec des projets comme pipenv ou poetry (je recommande d’ailleurs fortement ce dernier, et je vous invite à voter sur cette issue pour enfoncer le clou).

setup.cfg est la suite logique de tout ça, un format en texte brut, facile à manipuler pour le reste du monde.

Et ça a été bien fait:

  • setup.cfg marche out of the box. Il n’y a rien à faire de particulier : si vous aviez un setup.py, vous pouvez juste le convertir en setup.cfg. Si vous avez un nouveau projet, vous pouvez juste écrire le setup.cfg. Il n’y a pas de piège. Ça marche depuis… 2016. Ouais.
  • setup.cfg a une documentation décente. Si, si. C’est très améliorable, mais mieux que tout ce qu’on a jamais eu pour les premières années de setup.py
  • setup.cfg couvre la plupart des cas de figure que setup.py faisait et se permet de rajouter des goodies très cools. version = attr: src.__version__ inclue la version depuis __init__.py, "options.data_files" remplace le MANIFEST et "license = file: LICENCE.txt" injecte le corps de la licence depuis un fichier texte. Tous les hacks à la noix de setup.py ? Fini.
  • C’est simple à utiliser. Voici quelques projets pour démontrer le bouzin: exemple 1, exemple 2, exemple 3, exemple 4. Ça se comprend même sans lire la doc !
  • C’est compatible avec tous les outils legacy. En fait, il suffit de créer un fichier setup.py avec une seule ligne dedans (import setuptools; setuptools.setup()) et pouf, tout le worflow d’avant marche: python setup.py develop, python setup.py sdist upload, etc. Du coup, tout ce qui comprenanait setup.py (c’est à dire le putain de monde entier en Python) marche avec setup.cfg.

Donc setup.cfg, malgré des lacunes, c’est sympa.

Setup.cfg, ou es-tu ?

“Attend une minute, si c’est si bien que ça, pourquoi j’en ai jamais entendu parler ? Moi on m’a dit que setup.cfg il servait à rien…”

Je ne vous le fais pas dire !

Je n’ai aucune idée de la raison pour laquelle cette information ne circule pas plus. La seule raison pour laquelle je l’ai trouvée c’est parce que je passe des heures à faire de la veille informationnelle en Python, et que je suis tombé dessus au détour d’un carrefour, dans une ruelle sombre du Web. Et que j’ai pris le temps et le risque de le tester sur un de mes projets pour vérifier que oui, ça marche comme prévu.

Même la doc Python vous fait croire que setup.cfg c’est un artéfact vaguement utile pour une pauvre option monoligne.

Et merde, j’ai déjà écrit 900 articles sur ce blog, je ne peux pas mettre à ma charge d’avertir tout le monde pour chaque truc à savoir sur Python.

C’est comme pour le # -*- coding: utf-8 -*-. Un jour un mec utilisant Emacs a écrit un tuto avec ça, et tout le monde a copié-collé les hiéroglyphes alors qu’en fait # coding: utf8 est parfaitement valide. Ou comme nuikta, qui permet de compiler de Python de manière fiable, et que personne ne connait. Combien de temps doit-on gâcher avec des trucs comme ça ?

pyproject.toml vient foutre le bordel

Maintenant, figurez-vous que nous avons un groupe de personnes chargées de faire évoluer la situation du packaging, la Python Packaging Authority, ou PyPA.

Une très bonne initiative, vu que le free-for-all du passé ne nous avait pas trop réussi. Et un succès puisque la situation actuelle en packaging Python est maintenant beaucoup plus propre.

Et on en a besoin. En effet, setup.cfg doit être amélioré car il a certains défauts. Le format n’est pas parfaitement standardisé. Seule la doc fait figure de description, et pour l’instant setup.cfg, c’est whatever configparser comprends, sachant que configparser parse les trucs au motoculteur, et selon la version de Python. C’est pas gravissime, et ça n’a pas vraiment été un problème jusque là, mais pour la pérennité de la chose, une consolidation est nécessaire. On doit avoir une spec solide, un parseur robuste et stable, etc.

Sachant la purge qu’a été l’historique du packaging en Python, on s’attend donc a ce que la PyPA fasse le choix de standardiser setup.cfg, qui marche depuis 2 ans, fait le job, est compatible avec l’existant et résout déjà le problème de permettre au reste du monde de manipuler les données du package en texte brut. Puis, comme un bon groupe de décision sage, elle va proposer des solutions incrémentales aux défauts de setup.cfg et son écosystème.

Un exemple possible serait de s’allier avec l’équipe de setuptools pour figer le format setup.cfg, clarifier les edge cases de ce INI en particulier, et si besoin, créer une alternative a configparser (ou figer configparser) afin d’obtenir une implémentation de référence irréprochable pour parser le format. Puis désigner ce format comme le nouveau standard, en version 1 implicite, et le documenter puis en faire la promotion afin que tous les nouveaux outils puissent l’utiliser. Ensuite, ayant identifié des limitations, on crée le successeur de ce format, qui aura le même nom, mais un header de version qui lui sera explicite afin de permettre aux parseurs de s’adapter. Et on fait en sorte que cette nouvelle version soit plus propre, plus belle, super green. Et on l’introduit aussi progressivement et en douceur qu’un sex toy anal.

Bref, on s’attend à ce que la PyPA nous amène vers une totale liberté de pensée cosmique vers un nouvel âge réminiscence.

Sauf que non.

Ces ânes ont décidé… de créer un nouveau format et ignorer tout l’existant.

Fuck. That. Je hais que ce dessin de XKCD puisse être d’actualité encore et encore, chaque mois que l’humanité passe:
Le packaging en Python, c'est une forme de solidarité masochiste avec la communauté JS

Je ne comprends pas cette décision, et leurs justifications sont d’une grande faiblesse, pour ne pas dire insultante pour une communauté qui en a marre de payer le prix de l’égo des gens qui ont envie d’avoir leur nom sur la nouvelle barre de fer officielle.

C’est d’autant plus étrange que la PyPA n’est pas composée de cons. Non. On a Brett Cannon (core dev, qui fait un travail exceptionnel avec Python VSCode), Nathaniel Smith (qui nous a révolutionné l’async avec trio) et Kenneth Reitz (l’auteur de Python requests).

Comment ce pet de cerveau a-t-il pu émané de ces brillantes personnes, je ne le sais.

Mais je vous invite tous à non seulement utiliser setup.cfg en masse, mais aussi à activement contester cette décision sur tous les mediums à votre disposition.

Parce qu’évidemment je l’ai faits, et comme d’hab, ils font la sourde oreille. Et on va en payer le prix. Mais bon, depuis le temps, vous avez l’habitude. C’est pas comme si j’avais pas déjà annoncé que redux et dockers étaient overkill pour la plupart des projets, que le NoSQL allait avoir un retour de baton, que vue était génial, flask PAS pour les débutants, bitcoin intéressant, pipenv –three stupide, python parfait pour l’enseignement… des années avant. Pour les consultations de boule de cristal, c’est uniquement le mardi à 15h.

N’est-ce pas trop tard ?

Au contraire, c’est exactement le bon moment. Le format le plus utilisé actuellement n’est ni le setup.cfg, ni le pyproject.toml, mais toujours le bon vieux setup.py. En fait, n’en déplaise aux defenseurs de pyproject.toml qui veulent nous faire croire que le projet est bien plus populaire et avancé qu’il ne l’est vraiment, il vaut l’équivalent d’un draft de proposal en beta testing. Une simple recherche github retourne:

De toute façon la transition sera longue, et les outils commencent à peine à supporter le nouveau format. En fait, ils ne sont même pas d’accord sur comment l’utiliser. On n’a pas de standard pour le lock file (pipfile semble se dégager, mais n’est pas enterriné) et les outils utilisent pyproject.toml en créant une section custo dedans au lieu des champs standardisés (poetry), voire pas du tout comme pipenv. De plus, setup.cfg est déjà utilisé dans la nature (voir les exemples plus haut).

Par ailleurs, extraire les données de setup.cfg plutôt que de pyproject.toml n’est pas très compliqué, les outils de packaging n’ont qu’une toute petite partie de leur code dédié à la gestion du fichier, le reste c’est la logique de management des dépendances, le téléchargement, la command line, le virtualenv, etc. Ils peuvent par ailleurs tout à fait supporter les deux pendant la transition.

Vu que c’est nous qui allons nous coltiner les conséquences du choix pour les 10 prochaines années à venir, autant élever la voie. D’autant que, surprise, ça ne coûte quasiment rien à la majorité des projets de migrer ou commencer avec setup.cfg. Tout marche déjà, et on peut produire de jolies wheels. On ne peut pas dire autant de pyproject qui demande d’adopter des outils tout neufs et qui doivent encore faire leur preuve. Aussi, bonne chance pour faire marcher votre toolchain de CI avec, j’ai essayé, et croyez moi c’est relou.

Maintenant, j’aime que le packaging avance. J’aime même les nouveaux outils comme poetry. Mais la suite n’est pas inéluctable, et on peut concilier modernité avec sanité.

]]>
25060
Programmation par contrat avec assert http://sametmax.com/programmation-par-contrat-avec-assert/ Sat, 08 Sep 2018 19:40:11 +0000 http://sametmax.com/?p=24956 Le mot clé assert est populaire en Python essentiellement grâce à la lib pytest, dont on vous a parlé dans le dossier sur les tests unitaires.

En dehors de ce cas d’usage, personne ne comprend bien son utilité.

Déjà, dans les tutoriaux, on vous signale de ne pas l’utiliser pour faire des vérifications importantes, à cause d’une particularité d’assert: il peut disparaitre à tout moment !

En effet, si vous lancez Python toutes voiles dehors avec l’option -o (pour “optimize”), les lignes contenant ce mot clé sont ignorées.

Alors, mille millions de mille sabords, pourquoi se faire chier à avoir ajouté ce truc ?

A quoi un machin qui peut disparaitre à tout instant peut-il bien servir ?

Well, it’s not a bug, it’s a feature, my dear.

Voyez-vous, on connait souvent le premier effet kisskool d’assert:

    assert condition

Et si la condition est fausse, on se tape une exception AssertionError.

En revanche, ce que les gens savent moins, c’est qu’il existe une seconde forme, beaucoup plus utile:

    assert condition, "Message en cas d'erreur"

Qui fait tout pareil, mais permet de donner un feedback a qui tombe sur l’erreur susnommée.

Et c’est là que c’est intéressant: parce que ça vous permet de mettre des vérifications complexes dans le code, qui vont permettre aux devs d’éviter les âneries. C’est ce qu’on appelle un contrat.

Imaginez une fonction qui prend en paramètre une valeur, qui est la clé d’un dictionnaire:

    ARTIBUSES = {
        'truc': 1,
        'bidule': 1,
        'machin': '2',
        'chose': '3',
        'chouette': '3',
        'foo': '4',
        # ...
    }
    
    def souquer_les_artibuses(artibuse):
    
        target = ARTIBUSES[artibuse]
        # un code de souquage professionnel s'ensuit

Si quelqu’un utilise votre code, et insère la mauvaise artibuse (le vil flibustier !), notre fonction va (s’)échouer sur une KeyError. Ceci va obliger le contrevenant (le perfide faquin !) à regarder dans le code source et comprendre le code pour trouver la source de son désarroi. Quelle perte de temps pour notre dev (le fils de pute !).

Une solution est alors de mettre:

    def souquer_les_artibuses(artibuse):
        if artibuse not in ARTIBUSES:
            raise ValueError(
                f"'{artibuse}'' n'est pas une artibuse valide. "
                f"Utilisez une valeur parmi: {', '.join(ARTIBUSES)}"
            )
        target = ARTIBUSES[artibuse]

Et c’est certes une bonne solution, claire et explicite.

Mais elle a un défaut majeur: à chaque appel, on rajoute le poids d’un test qui n’a aucun intérêt pour le fonctionnement normal du programme. En fait, la majorité des appels de cette fonction se feront avec les bonnes valeurs (logique sinon le code planterait), et donc notre test est un poids superflu.

Ce problème se cumule quand les tests deviennent plus nombreux, plus complexes, et plus lourds, tandis que la fonction est appelée de plus en plus de fois.

Comment concilier donc son besoin impérieux d’aider son prochain, qui est probablement soi-même un vendredi à 3h du mat après un push qu’on n’aurait pas du faire en fin de semaine, et éviter ce gâchis de ressource ?

Ventre-saint-gris de sa race ! Avec assert bien entendu !


    def souquer_les_artibuses(artibuse):

        assert artibuse in ARTIBUSES, (
            f"'{artibuse}'' n'est pas une artibuse valide. "
            f"Utilisez une valeur parmi: {', '.join(ARTIBUSES)}}"
        )

        target = ARTIBUSES[artibuse]

En dev, ou quand on debug en production, on obtient toutes les vérifications et infos nécessaires. Le contrat à remplir avec notre fonction est vérifié. On peut mettre autant de clauses à notre contrat qu’on le souhaite, aussi lourdes qu’on veut !

Quand on est prêt à relancer la prod en mode propre, on exécute tout avec -o

Les vérifications faites en amont d’une fonction sont ce qu’on appelle des pré-conditions dans le jargon de la programmation par contrat. Ce sont les plus faciles à comprendre et les plus courantes. On peut néanmoins faire également des vérifications à la fin de la fonction, qui permette de vérifier qu’on est bien dans un état cohérent à la sortie de celle-ci.

C’est ce qu’on appelle des post-conditions: c’est moins courant, et moins facile à écrire, mais très puissant car on se souci d’un état final (est-ce que mon résultat est > 3 ?), et non de comment on y arrive. Un moyen bien plus souple d’attraper des erreurs plutôt que de chercher toutes les combinaisons de ce qu’on peut mal faire.

Effets secondaires de -o

Le saviez-vous ? Python vient aussi avec une variable magique, __debug__, qui est à True par défaut. Utiliser -o met cette variable a False. Plus fort encore, toute condition sur __debug__ sera retirée du code !

if __debug__:
    un_dump_log_de_porc()

Pouf, avec -o, plus de dump.

Il existe même -oo pour retirer en plus les docstrings, et économiser un peu de mémoire.

Le problème avec assert

Le problème avec assert, c’est vous. Enfin je dis vous, le vous du passé, celui qui ne savait pas. En effet, plein de devs vont utiliser des assert dans leur code sans penser à mal. Et vous arrivez avec votre -o, et boom, le code est tout cassé. C’est bien con de ne pas pouvoir stripper ses assert à soi sous prétexte qu’une dépendance est codée par un connard Bachi-bouzouk.

Or assert est très souple, et permet de tester littéralement n’importe quoi. Du coup, cela demande un peu d’expérience pour savoir quand l’utiliser, et quand ne pas le faire.

Le concept général est: si votre erreur concerne la fonctionnalité fondamentale de votre fonction, levez toujours une exception. Si en revanche vous testez si les valeurs des paramètres correspondent à quelque chose de sain, et qu’une erreur n’aurait pas d’effet de bord, vous pouvez (ce n’est pas du tout obligatoire, une exception normale reste un usage correct) utiliser assert.

Donc, n’utilisez pas assert pour tester la logique de votre programme, ni pour protéger l’intégrité de vos données face à une erreur.

Une dernière chose: n’utilisez pas assert pour fournir un contrat basé sur les types (comme par exemple, appeler isinstance()), puisque mypy et les types hints le font déjà et qu’ils sont maintenant très agréables à utiliser.

]]>
24956
Sam ne répondra plus http://sametmax.com/sam-ne-repondra-plus/ http://sametmax.com/sam-ne-repondra-plus/#comments Tue, 04 Sep 2018 01:18:35 +0000 http://sametmax.com/?p=24942 Si vous nous avez écrit un email, vous savez qu’on a tendance a être lents pour répondre. En fait, parfois plus d’un an :)

Mais que ce soit sur twitter, reddit, indexerror, mastodon, les commentaires du blog ou par email, on finit par répondre à tout le monde. Les ratés existent, mais ils sont rares. La seule catastrophe étant notre bug tracker, parce que j’en fais suffisamment au boulot pour ne pas avoir le courage de le faire sur mon temps libre.

Gérer toute cette communauté, ça a un coût. J’y ai joyeusement participé pendant toutes ces années, parce que j’aime profondément aider les gens.

Notez que je ne me prends pas pour mère Thérésa, à moins qu’elle ai déjà fait du SM et vendu du viagra illégalement. Mais donner un coup de main a toujours été quelque chose d’important pour moi.

C’est la raison pour laquelle j’aime donner des formations. Parfois gratuitement. Parfois dans le cadre associatif.

C’est la raison pour laquelle je me déplace à des confs bien loin pour donner des talks même si personne ne sait que c’est moi. Parfois je discute avec un groupe de gens enthousiastes de ce super site NSFW. C’est vrai qu’il est bien. Vous l’avez connu comment ?

C’est la raison de la qualité des articles du blog également. Je passe un temps déraisonnable à me demander comment je vais pouvoir aider au mieux le lecteur avec une explication. Il m’est arrivé plusieurs fois de passer une journée entière sur un seul article compliqué, aux dépens d’un client que j’aurais pu facturer. Si on devait chiffrer le coût de ce site, je pense que ça me paierait un joli tour du monde.

Alors oui, ça a un prix, et je l’ai payé avec plaisir. Mes amis, qui m’ont entendu cracher sur les réseaux sociaux, ont même été étonnés de voir mon investissement sur Hacker news ou Twitter, au fil des années.

En cette fin 2018, je vais changer mon implication. J’ai envie de récupérer du temps libre, diminuer la tentation des interruptions et renforcer ma capacité d’attention, ce qui veut dire trancher dans le gras des distractions. Moins de surf sans but, moins de streaming, et moins de présence en ligne.

J’ai pensé à arrêter complètement le blog, mais soyons honnête, je n’écris plus au dixième du rythme de ses débuts, donc ça ne me prend pas si longtemps. Et un article de temps en temps, c’est thérapeutique. Je pense que j’ai besoin de mon troll JavaScript annuel pour des raisons de santé.

Ce qui me prend du temps par contre, c’est tout ce qui est autour des articles.

Donc, à partir de cette semaine, je vais arrêter de répondre sur Twitter, le subreddit (qui n’a de toute façon jamais pris) et indexerror. Twitter en particulier sera donc de nouveau en sens unique, comme à nos touts débuts: je poste, et je ne réponds pas.

Pour les commentaires du blog, j’ai fermé la création de compte, et j’ai autorisé les commentaires uniquement à ceux qui avaient déjà un compte. Je ne répondrai pas pour autant, et je sais que la communauté existante ne foutra pas le bordel, tout en leur permettant de glisser les corrections des mes coquilles débiles.

Pour mastodon, je vais fermer le compte complètement. Enfin le passer en privé et l’oublier car framapiaf ne permet pas la supression. L’expérience est réussie, je pense que c’est une bonne plateforme, mais le ratio taff/retour est trop bas pour mes nouveaux objectifs.

Quant aux mails, pour le moment je garde le canal ouvert. Ce qui devrait me permettre du coup de répondre beaucoup, beaucoup plus rapidement.

Je vous aime quand même, hein.

]]>
http://sametmax.com/sam-ne-repondra-plus/feed/ 2 24942
La débâcle de async en 3.7 http://sametmax.com/la-debacle-de-async-en-3-7/ http://sametmax.com/la-debacle-de-async-en-3-7/#comments Tue, 07 Aug 2018 13:14:40 +0000 http://sametmax.com/?p=24891 async et await ont été introduits en Python 3.5, tout le monde a trouvé l'idée formidable. D'ailleurs, ça a été intégré à JavaScript. Malheureusement, introduire des mots clés dans un langage est une opération très délicate.]]> Quand les nouveaux mots clés async et await ont été introduits en Python 3.5, tout le monde a trouvé l’idée formidable. D’ailleurs, ça a été intégré à JavaScript.

Malheureusement, introduire des mots clés dans un langage est une opération très délicate.

Limites et contournements des mots clés

En Python les mots clés ont une caractéristique importante : on ne peut pas les utiliser pour quoi que ce soit d’autre.

Par exemple, class est un mot clé, donc je ne peux pas créer une variable, un attribut, ou une fonction appelé class. Ceci lève une erreur:

>>> class = 1
  File "", line 1
    class = 1
          ^
SyntaxError: invalid syntax
>>> class Foo: pass
... 
>>> Foo.class = 1
  File "", line 1
    Foo.class = 1
            ^
SyntaxError: invalid syntax
>>> 

Pour cette raison, quand on veut qu’une variable contienne une classe en Python, on la nomme cls:

>>> class Bar:
...     @classmethod
...     def wololo(cls):
...         print(cls, 'wololo')
... 
>>> 
>>> Bar.wololo()
 wololo
>>> 

C’est aussi pour cela que vous voyez parfois des variables nommées truc_. Souvent from_ par exemple, parce que from est un mot clé.

(pro tip: plutôt que from et to, utilisez src et dest)

Quand en Python 2 on a introduit True et False, un gros problème s’ensuivit: soit on en faisait des mots clés, et on pétait tout le code précédent qui utilisait ces mots, soit on en faisait des variables globales.

Le choix a été de garder la stabilité jusqu’à la prochaine version majeure, et c’est pour cela que:

  • On peut faire True = False en Python 2. Ouch.
  • Python 3 casse ce comportement, et donc ça fait une chose de plus à laquelle il faut penser quand on migre.

Pour la 3.5, on avait donc ce même problème, avec une cerise sur le gâteau: la lib standard utilisait elle-même la fonction asyncio.async.

Le choix a donc de faire de async / await des variables globales, et de les transformer en mot clé en 3.7.

En 3.6, un warning a été ajouté pour rappeler aux gens de migrer leur code.

C’est un sacré taf, et ça comporte des risques comme nous allons le voir plus loin. C’est pour cette raison que l’ajout d’un mot clé dans Python est une des choses les plus difficiles à faire passer sur la mailling list python-idea.

Arrive la 3.7

La 3.7 est sortie avec tout un tas de goodies. Youpi. Mais aussi avec le passage de async/await de variables globales à mots clés, cassant la compatibilité ascendante. Quelque chose de rare en Python, et que personnellement j’aurais réservé pour Python 4, ne serait-ce que pour respecter semver.

Le résultat, tout un tas de systèmes ont pété: des linux en rolling release, des gens qui ont fait l’update de Python à la main, des gens qui maintiennent des libs compatibles 3.5 a 3.7…

D’autant que la 3.5 a asyncio.async, mais 3.7 considère ça une erreur.

Petit exemple avec l’impact sur debian.

Comment on aurait pu éviter ce merdier ?

D’abord, il aurait fallu ne pas introduire asyncio à l’arrache. Dans mon “au revoir” à Guido, je disais que je trouvais que les dernières fonctionnalités majeures de Python avaient été mises en oeuvre de manière précipitée.

Cela se vérifie encore et encore avec asyncio, dont il faudra que je fasse un article pour dire tout ce qui a mal tourné.

Casser la compatibilité ascendante dans une version mineure n’est pas acceptable, même si les dégâts sont limités et qu’on y survivra très bien.

Le fait qu’asyncio soit une API marquée comme “provisional” n’a jamais empêché quelqu’un d’appeler ses variables async. Après tout on utilise les threads depuis bien longtemps.

L’autre problème vient de l’amateurisme qui se glisse de plus en plus dans le dev.

C’est une bonne chose, parce que ça veut dire que la programmation est de plus en plus accessible et accueille de plus en plus de monde.

Mais cela veut dire aussi qu’une grosse part la population de programmeurs est aujourd’hui constituée de personnes qui n’ont ni les connaissances, compétences ou ressources pour faire les choses correctement.

On le voit particulièrement dans le monde JavaScript, ou c’est l’explosion (là encore, ça mérite un nouvel article). Mais l’exemple de la 3.7 nous montre que la communauté Python n’est pas immunisée, et je pense que le problème va s’amplifier.

Que veux-je dire par là ?

Et bien il y a 30 ans, cela ne serait pas venu à l’esprit de la plupart des devs de compiler quelques choses sans mettre les flags en mode parano pour voir ce qui allait péter. Après tout, quand on code en C, on sait que tout peut imploser à tout moment, alors la prudence est une question de culture.

Aujourd’hui par contre, la majorité des devs des langages haut niveau écrivent du code, font quelques tests à la main, et publient ça. D’autres les utilisent. Font des mises à jour en masse. Aucun ne prennent le temps ne serait-ce que d’activer les warnings les plus basiques.

Comme tout est facile à première vue, et c’est quelque chose dont on fait la promotion pédagogiquement parlant, car ça incite les gens à se lancer, on oublie la complexité inhérente à la programmation.

Mais il y a une différence colossale entre avoir un code qui marche une fois sur sa machine, et un code prêt pour la production.

Par exemple en Python, vous pouvez demander l’activation des warning pour chaque appel avec:

python -Wd

En 3.6, ça implique ceci:

>>> def async():
...     pass
... 
:1: DeprecationWarning: 'async' and 'await' will become reserved keywords in Python 3.7

L’info a toujours été là. Prête à être utilisée.

Mais alors pourquoi ne pas afficher tous les warnings, tout le temps ?

Et bien si je le fais:

python -Wa

Voilà ce que ça donne quand je lance juste le shell de python 3.6:

Voir le code sur 0bin.

Vous comprenez donc bien que ce n’est PAS activé par défaut. En fait, originalement le message était dans le corps de l’article, mais j’ai du le mettre sur 0bin parce que ça faisait planter WordPress. Si.

A chaque upgrade, il est important de vérifier les warnings pour préparer ses migrations futures.

Oui, c’est du boulot.

En fait…

La programmation, c’est BEAUCOUP de boulot

Même si on arrive maintenant à extraire une frame vidéo en gif en une ligne de commande.

Surtout maintenant qu’on y arrive en fait, car on multiplie les agencements hétérogènes de boites noires pour créer nos merveilleux programmes qui font le café.

Alors on prend des raccourcis.

Et puis aussi, parce qu’on ne sait pas. Qui parmi les lecteurs du blog, pourtant du coup appartenant à la toute petite bulle des gens très intéressés par la technique, connaissaient le rôle des warnings et comment les activer ?

Mais ce n’est pas le seul problème. Il y a clairement une question d’attentes et de moyen.

L’utilisateur (ou le client) final veut toujours plus, pour moins cher, et plus vite !

Et le programmeur veut se faire chier le moins possible.

Comme la complexité des empilements d’abstractions augmente, cela conduit à ignorer ce sur quoi on se base pour créer ce qui doit combler notre satisfaction immédiate.

J’ai parlé d’amateurs plus haut.

Mais je ne parle pas simplement de mes élèves. De mes lecteurs.

Je parle aussi de moi.

Prenez 0bin par exemple.

Il n’est plus à jour. Il n’a pas de tests unitaires. Il a des bugs ouverts depuis des années.

Ce n’est pas pro du tout.

Sauf que je ne suis pas payé pour m’en occuper, et c’est bien une partie du problème: nous sommes de nombreux bénévoles à faire tourner la machine a produire du logiciel aujourd’hui. Donc si je n’ai pas envie, fuck it !

Vous imaginez si l’industrie du bâtiment ou celle de l’automobile tournaient sur les mêmes principes ?

La moitié des dessins industriels faits par des bloggers, des étudiants, des retraités, des profs de lycées, des géographes, de biologistes et des postes administratifs ?

Des immeubles et des voitures dont des pièces sont fabriquées par des potes qui chattent sur IRC et s’en occupent quand ils ont le temps ? Gratuitement. Y compris le service après-vente.

Alors que les usagers veulent toujours plus: des normes sismiques et de la conduite autonome. Tout le monde le fait, alors la maison de campagne et la fiat punto, c’est mort, personne ne l’utilisera.

Difficile de maintenir la qualité à cette échelle.

Il y a tellement de demandes de dev, jamais assez d’offres, de ressources toujours limitées.

Et ça grossit. Ça grossit !

Aides techniques

Ceci dit, à l’échelle de la PSF, ça aurait dû être évité.

Avant d’aborder les aides techniques, il serait bon d’arrêter les conneries. Je me répète, mais c’était une vaste dauberie de faire passer async/await en mot clé avant Python 4.

J’ai parfaitement conscience du besoin de faire progresser un langage pour ne pas rester coincé dans le passé. Je suis pour async/await, très bonne idée, superbe ajout. Mettre un warning ? Parfait ! Mais on respecte semver s’il vous plait. Si vous avez envie de faciliter la transition, mettre un import __future__, et inciter les linters à faire leur taff.

En attendant, pour la suite, Python va faciliter le debuggage.

Par exemple, depuis la 3.7, les DeprecationWarning sont activés par défaut au moins dans le module __main__. Donc un développeur verra ses conneries bien plus rapidement.

E.G:

Imp est déprécié en 3.6, mais sans -Wd, on ne le voit pas:

$ python3.6
Python 3.6.5 (default, May  3 2018, 10:08:28) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import imp

En 3.7, plein de modules importent imp, mais les DeprecationWarning ne sont pas montrés, car ça arrive dans des codes importés. En revanche, si dans le module principal, vous importez imp:

$ python3.7 
Python 3.7.0+ (default, Jun 28 2018, 14:08:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import imp
__main__:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses

Ça donne une info importante, sans foutre un mur de warnings à chaque lancement.

Une autre aide est l’apparition, toujours en 3.7, du mode développement de Python avec -X dev qui active tout un tas de comportements aidant au développement:

  • active -Wd
  • appelle PyMem_SetupDebugHooks
  • active faulthandler
  • active le mode debug de asyncio
  • met sys.flags.dev_mode sur True

Évidemment, tout ça ne sert pas à grand-chose si on ne sait pas ce qu’il faut en faire. Et ça demande du temps et du travail, ce que l’amateurisme ne permet pas forcément.

Enfin je dis ça. La plupart des employeurs s’attendent à tout, tout de suite également. Donc au final, n’est-ce pas la culture générale de notre industrie qui est en train de virer dangereusement vers le vite fait mal fait ?

Même si il y a clairement une question de compétence (un prof de maths est généralement compétent en maths, alors que j’attends toujours de rencontrer un prof d’info qui est capable de mettre quelque chose en prod), la pression du marché a créé des attentes impossibles…

L’informatique n’existe comme secteur économique que depuis quelques décennies, contre des siècles pour la plupart des autres disciplines scientifiques. Pourtant on exige d’elle le même niveau de productivité. Il a bien fallut rogner quelque part, et c’est la fiabilité qu’on a choisit.

Quand il y 20 ans, on rigolait en comparant le debuggage de Windows a la réparation d’une voiture, et la punchline sur le redémarrage, ce n’était pas grave: un peu de virtuel dans un monde plein d’encyclopédies papier, de cabines ou bottins téléphoniques et autres cartes routières.

Aujourd’hui que notre monde entier dépend du fonctionnement de nos conneries codées à l’arrache, c’est plus emmerdant. Et ça explique aussi pourquoi le téléphone de ma grand mère fonctionne toujours mieux pour faire des appels que mon putain de smartphone a 600 euros. Mais je peux draguer une meuf par texto en faisant caca à l’aéroport. Tout a un prix.

]]>
http://sametmax.com/la-debacle-de-async-en-3-7/feed/ 27 24891