PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Quels exercices pour débutants en Python ? 40

mercredi 6 mai 2015 à 20:51

Quand quelqu’un commence à programmer avec Python, il faut lui apprendre les bases : les conditions, les boucles, les listes, les dicos, les fichiers, les fonctions, les classes et tout le bordel.

Pour que ça rentre, et pour identifier les points à consolider, les exercices sont indispensables.

Malheureusement un bon exercice est très dur à construire :

Il y a des grands classiques :

Certains ne sont plus possibles : parcourir l’API tweeter par exemple est devenu beaucoup trop compliqué alors qu’avant c’était un truc super fun à faire faire.

D’autres sont vachement plus faciles : s’envoyer un sms d’alerte si il va pleuvoir aujourd’hui est un jeu d’enfant si on est chez free qui a une URL pour ça.

Est-ce que vous avez des exos sympas à proposer ? Je cherche l’inspiration.

Si vous postez un exo, merci d’y joindre les notions testées et les pré-requis ainsi que, si nécessaire, le public visé.

Pourquoi obliger le SSL par défaut est une grosse connerie 36

mercredi 6 mai 2015 à 11:06

Chrome et Mozilla parlent de limiter les fonctionnalités de leurs navigateurs sur les sites qui n’utilisent pas SSL.

Sur le papier, c’est pavé de bonnes intentions : forcer les auteurs de sites à chiffrer la communication entre le serveur et le client. Plus de sécurité pour les utilisateurs, tant pis pour les dev paresseux, pas vrai ?

Sauf que HTTPS fonctionne sur le principe d’autorités centrales. Vous devez obtenir un certificat auprès d’une autorité de confiance, sans quoi le navigateur va afficher ça :

This connection is untrusted

Michou ne va pas lire ce texte, encore moins chercher et trouver comment accéder au site. Elle va courir. Un virus !

Les moteurs de recherche vont vous plomber, l’utilisateur va fuir, bref, votre site est mort.

Et c’est la que ça devient fun, car les autorités centrales sont des organismes privés, et il n’y en a pas beaucoup. Ils ne sont pas tenus de vous filer un certificat, et peuvent le retirer à tout moment.

En clair, forcer SSL partout, c’est donner la main à quelques grosses boites pour décider qui a le droit d’être sur le Web ou pas. Vous voulez faire un truc qui ne leur plait pas ? Attention, montrer un tableau classique avec des nichons ne plait pas à Apple, faire un noeud Tor ne plait pas à Amazon, les bitcoins ne plaisent pas à Paypal et être gay ne plait pas à plein de gouvernements.

Toutes ces entités travaillent main dans la main avec les autorités de certification, et avoir Mozilla en proposer une n’y changera rien. Si vous faites un truc que ces gens ne veulent pas, vous effacer du Web sera aussi facile que de faire pression pour révoquer votre certificat.

Sans jugement. Sans procès. Sans recours possible.

Le Web deviendra un app store.

EDIT :

Je rajoute mon commentaire sur let’s encrypt ici sinon je sens que tout le monde va radoter

L’article a un lien qui pointe vers let’s encrypt. Ca ne change rien : c’est centralisé, privé, et subira des pressions comme tous les autres. J’aime Mozilla, j’ai confiance en eux, mais ils n’ont aucun moyen de garantir la neutralité de let’s encrypt, ne serait que par rapport aux gouvernements.

Et puis quoi ? A la fin tous les sites “border line” seront chez eux ? Comme ça bam, un bon single point of failure à attaquer pour tout faire tomber d’un coup ?

FBI. FBI. FBI.

Est-ce que cet outil existe en Python ? 12

vendredi 1 mai 2015 à 10:35

Le test unitaire le plus simple, c’est de vérifier que son API publique n’a pas changé.

Ça veut dire :

Ce sont des tests super cons qui demandent d’écrire du code du genre :

class Foo:
    bar = True
    def __init__(self, stuff=False):
        self.stuff
 
    def thing(self):
        return "doh"
 
# et dans les tests
self.assertTrue(hasattr(Foo, 'bar'))
self.assertTrue(hasattr(Foo, 'thing'))
self.assertTrue(hasattr(Foo(), 'stuff'))

Et personne ne le fait, car déjà les tests c’est relou, mais écrire des tautologies à la main, c’est au dela du tolérable.

Pourtant, ce genre de tests m’aurait déjà sauvé les gosses plusieurs fois. Une faute de frappe qui se glisse, un attribut qu’on rend privé, un méthode qu’on déplace et on pense avoir tout refactoré, un pote qui change un truc qu’on utilisait, etc.

Je me demandais donc si il existait un truc qui permette de faire :

self.assertStablePublicAPI("package.module") # scanne tout le module et tous les objets
self.assertStablePublicAPI("package.module:Class") # check juste la classe
self.assertStablePublicAPI("package.module:func") # check juste la function

Le but étant que quand on lance les test, ça check si ça rencontre un nouvel objet/attribut/param et sauvegarde tout un dans un JSON. Aux prochains tests, si on a modifier l’API publique, le test foire jusqu’à ce qu’on modifie explicitement le fichier JSON.

On doit bien entendu pouvoir régler le truc pour ignorer ou ajouter explicitement des choses à checker, par exemple par défaut ça ignore tous les machins avec des underscores dans le nom.

J’ai cherché sur le net, mais j’arrive pas à trouver ça. Des pistes ?

Le débat sur la programmation fonctionnelle en Python 75

mardi 28 avril 2015 à 09:35

L’écosystème Python est en ébullition depuis la 3.4 et asyncio, tout le monde se sent pousser des ailes de faire évoluer la stack. La 3.5 vient réintroduire le fameux opérateur % pour les bytes, sa disparition ayant fait bien chier tous les développeurs réseaux, on rajoute l’opérateur @ (qui ne fait rien, mais qui servira à numpy pour les opérations sur les matrices), il y a bien entendu le débat sur async/await et enfin le gros morceau : le type hinting.

C’est une période formidable pour la communauté car il rappelle que Python, un langage plus vieux que Java, un langage qui est maintenant considéré comme main stream – utilisé par des sociétés corporate et l’éducation nationale – ne restera pas sur ses acquis et va continuer à se réinventer.

Pour cette même raison, il existe un débat parallèle qui a lieu plutôt sur reddit, hackernews, la mailling list de Python-dev et Python-idea ou par PR github interposées sur des projets type pyrsistent. Il s’agit de faire de Python un langage fonctionnel.

La programmation fonctionnelle, c’est quoi ?

Ce n’est pas juste utiliser des fonctions.

Il y a plusieurs caractéristiques à la prog fonctionnelle, mais la plus importante est que tout est immutable (les données ne peuvent être modifiées), et par corollaire, que rien n’a d’effet de bord.

Ce qui signifie que dans un langage purement fonctionnel, une structure comme la liste n’aurait pas de méthode qui ne retourne rien comme append() ou pop().

liste.append(element) serait remplacé par liste + [element].

element = liste.pop() serait remplacé par liste, element = liste[:-1], liste[-1]

Vous notez qu’on ne modifie pas la liste initiale, on recrée de nouvelles structures de données à chaque fois.

Une alternative serait que ces méthodes retournent une nouvelle liste plutôt que None.

Bref, c’est comme si on avait que des tuples :)

Bien qu’on puisse créer des formes de programmations orientées objet qui n’impliquent pas de mutabilité, dans les faits les implémentations sont pour la plupart basées justement sur le fait qu’on puisse modifier les objets, aussi on oppose souvent FP et OOP.

Une autre caractéristique est qu’on doit pouvoir remplacer toute expression par son résultat suivant sans changer le sens du programme. C’est très élégant :

[1, 2, 3] + [4] peut être réécrit [1, 2, 3, 4], et le programme aura toujours le même sens. Il n’y a pas de notion de référence dont il faut se soucier, seulement les valeurs. Cela élimine naturellement un paquet de bugs et il n’y a pas de bonne pratique à appliquer ou apprendre sur la modification des données puisque le langage vous force à coder proprement.

En programmation fonctionnelle, la récursion est donc souvent mise en avant. Par exemple, supposons qu’on calcule la taille d’une liste ainsi en programmation traditionnelle :

def size(l):
    s = 0
    for x in l:
        s += 1
    return s

Voici une forme possible en programmation fonctionnelle :

def size(l):
    if not l:
        return 0
    return 1 + size(l[1:])

La raison pour cela est qu’on peut substituer chaque expression à son résultat. Avec l = [1, 2, 3]:

size(l) devient size([1, 2, 3]), qui devient 1 + size([2, 3]) qui devient 1 + 1 + size([3]) qui devient 1 + 1 + 1 + size([]) qui devient 1 + 1 + 1 + 0 qui devient 4.

Quelle que soit la forme remplacée, le fonctionnement du programme est rigoureusement identique.

Il devient alors plus facile de prouver qu’un programme marche, de le tester, et surtout, de travailler de manière concurrente : si rien ne se modifie, nul besoin de lock et de synchronisation car rien n’est partagé et toute instruction a toujours le même comportement quel que soit le contexte.

Python possède déjà des features fonctionnelles

Comme en Python on peut programmer en utilisant plusieurs paradigmes, il n’y a rien d’étonnant à ce qu’on retrouve des amateurs de plusieurs écoles dans les core devs et les outils qui vont avec.

Bien que les listes, les sets, les objets et les dictionnaires soient mutables, les tuples, les chaînes et les entiers ne le sont pas. Par ailleurs, les fonctions sont “des citoyens de première classe”, ce qui est considéré comme un pré-requis pour la programmation fonctionnelle. Cette expression signifie qu’on peut manipuler les fonctions elles-mêmes, les créer dynamiquement, les retourner et les passer en paramètre (c.f: les décorateurs).

Même si ce n’est pas explicitement nécessaire pour faire de la programmation fonctionnelle, tout langage fonctionnel sérieux vient avec 5 outils de base :

Python possède ces outils : map() et filter() sont des builtins, reduce() peut être trouvé dans le module functools et les fonctions anonymes peuvent être créés avec le mot clés lambda. Et bien sûr, une fonction peut s’appeler elle-même.

Voyons comment créer une liste de carrés de tous les nombres impairs de 0 à 9 :

carres = []
for x in range(10):
    if x % 2:
        carres.append(x * x)

La version fonctionnelle avec map()/filter() et des fonctions anonymes :

list(map(lambda x: x * x,  filter(lambda x: x % 2, range(10))))

Néanmoins Python va plus loin, puisqu’il propose les listes/sets/dictionnaires en intension, qui ne sont ni plus, ni moins que que map()/filter() intégrés sous forme de syntaxe :

[x * x for x in range(10) if x % 2]

Ce qui est non seulement plus concis mais plus clair et plus performant que les deux versions précédentes.

Donc, malgré des limites que nous verrons ensuite, Python est plutôt bien armé pour la programmation fonctionnelle. La présence des générateurs et tous les outils associés (itertools, functools, yield, yield from) viennent renforcer cet aspect orienté transformation plutôt que modification.

Il est par ailleurs déjà considéré comme une bonne pratique d’éviter les effets de bord le plus possible, d’utiliser des générateurs et des intensions. Bref, Python n’est pas le benêt de la programmation fonctionnelle.

Les limites de Python et de la programmation fonctionnelle

Malgré cela, il existe de sérieuses limites pour faire de Python un langage purement fonctionnel.

D’abord, les listes, les dicos et les sets sont mutables. Ensuite, la récursion est limitée par la taille de la stack (par défaut 1000 récursions), il n’y a aucune tail call optimisation. Enfin, les lambdas sont bridées à une expression.

Ceci n’est pas arrivé par accident, mais est le résultat d’un design du langage imposé par Guido Van Rossum, son créateur et BDFL.

D’abord, il faut bien se souvenir que raisonner pour modifier une structure de données, c’est beaucoup plus facile pour la machine ET l’humain que d’en recréer une.

Les algos de tris in place sont toujours plus rapides, les transformations in place prennent moins de mémoire, et l’accumulation est bien plus facile à concevoir, relire et debugger que la récursion.

J’ai déjà entendu des gens me soutenir que la FP était simple. Ces gens n’ont soit jamais essayé de former d’autres personnes à la programmation, soit sont des formateurs de merde. Je pense qu’au moins ce blog me donne un minimum de crédibilité sur mes qualités de pédagogue et je m’érige donc en tant que prophète de l’intelligibilité :

En vérité, je vous le dis, la programmation fonctionnelle, c’est dur à faire comprendre.

Erlang, Lisp et Haskel ne sont pas user friendly.

Ne me faites pas dire ce que je n’ai pas dis. Ce sont de beaux langages, des outils performants et utiles, mais Python est 100 fois plus facile à utiliser. Il n’y a aucune comparaison possible, ce n’est même pas sur la même échelle sur le graphe.

Ensuite, le monde n’est pas immutable : les choses ont des états à un instant t. En informatique, une connexion réseaux a un état, un système de fichier a un état, une session a un état, etc. Les langages fonctionnels ont donc recours à tout un tas d’astuces et d’enrobages pour les manipuler comme si ce n’était pas les cas. La fameuse “simplicité”, “élégance” et “pureté” qu’ils prônent en prend un coup.

Les meilleurs design patterns fonctionnels peuvent devenir un enfer pour travailler avec, les monades étant l’exemple le plus couramment cité. Déjà que c’est pas évident de faire passer la OOP, les générateurs, les classes et les décorateurs… Bonne chance pour les monades, et merci pour le poisson !

La limite de récursion et de la taille des lambdas est également parfaitement volontaire. Comprenez bien : tous les développeurs n’ont pas (et n’ont pas à avoir) le même niveau. Mettre un géographe devant une récursion avec injection de dépendance est aussi productif que de demander à un programmeur Web de calculer lui-même sa projection mercator.

Si demain la récursion devient plus facile, les dev chevronnés vont massivement l’utiliser même quand ce n’est pas indispensables : donnez un jouet à un geek, et il va s’en servir. Mais la lisibilité est primordiale, c’est une feature de Python. Les codes deviendront alors soudainement significativement plus durs à lire et à débugger, pour un gain minime. En effet, la récursion ne m’a JAMAIS manqué en Python. Les développeurs ne sont pas raisonnables, ils ne vont jamais se dire, je vais m’assurer que ce code sera facile à lire pour un débutant ou moi sans café, ils vont utiliser tous les outils à leur disposition. Guido a donc volontairement limité les outils à ce qui garde un code facile à appréhender.

C’est ce qui a fait le succès de Python jusqu’à présent. C’est un langage facile à prendre en main, et productif. Lire le code d’un autre est simple.

Lire le code Lisp d’un autre n’est PAS simple.

Une stack trace d’une erreur en pleine récursion est pleinement inutile.

Le callback hell de Javascript plein de fonctions anonymes en cascade est dégueulasse.

Vous allez me répondre : “les gens n’ont qu’a programmer proprement” où “il y a des outils pour ça”

Outre le fait que pouvoir coder en Python avec juste notepad et la console est une des ses meilleures features, c’est ignorer la nature humaine.

On pourrait penser que quelque chose d’aussi simple qu’une indentation propre et un espacement consistant est la base.

Mais non.

Jetez un œil à n’importe quel programme JS ou PHP de petite ou moyenne taille, et vous trouverez invariablement des blocs de traviole, des variables globales, des espaces qui dégueulent à droite et à gauche et des saloperies en tout genre.

Python force l’indentation. Python a un PEP8.

J’adore ça.

Ca amène les porcs à coder plus proprement.

C’est pareil pour les lambdas et la récursion. On force la main aux gens déraisonnables pour qu’ils ne fassent pas de connerie. Désolé, je n’ai pas confiance dans les barbus pour créer des choses user friendly. Et moi, j’aime ça, le code accessible. Fuck la pureté.

Si vous voulez des trucs tarabiscotés, Lisp le fera très bien.

Mais ce n’est pas Lisp qui a autant de succès, c’est Python. Et vous savez pourquoi ?

Parce qu’il est productif, facile, lisible, et avec une énorme communauté pleine de diversité car il est accessible à tout ce monde.

Bref, vous l’avez compris, je suis contre faire de Python un langage purement fonctionnel si c’est au prix de tout cela.

Maintenant, il existera peut être des moyens techniques pour rendre des implémentations d’outils fonctionnels aussi performants et lisibles que leurs versions non fonctionnelles.

Ca a été le cas avec les intensions : de longues chaines de map() et filter() illisibles deviennent claires avec une belle intension. Et c’est clairement un gain par rapport au for + append() traditionnel. Mais pour les cas moins simple, on a toujours la possibilité d’utiliser cette méthode, qui est facile à lire, à comprendre et à débugger.

Je gage qu’un compromis intelligent, voire carrément malin, puisse être trouvé en la matière.

Mais s’il vous plait, ne cassez pas Python juste pour avoir un nouveau jouet, vous scierez la branche sur laquelle nous sommes tous assis en train de boire l’apéro en rigolant.

C’est un reproche qui a été fait au type hinting : si on offre cette possibilité, elle sera utilisée, et tout code avec ces annotations est significativement plus moche. Si demain la majorité des projets en font un pré-requis, beurk.

Je ne pense pas que ça arrivera car c’est un sacré effort supplémentaire de les écrire. Donc on le fera uniquement si ça vaut le coup : par exemple un gros projet ou une lib avec une bonne base d’utilisateur.

Ce n’est pas le cas pour la programmation fonctionnelle : son coût d’écriture est beaucoup plus bas que celui de la relecture. On paie plus tard. Et ça les gens ne savent pas gérer. L’humain est naze pour investir dans le futur, c’est pour ça que nous consommons les ressources de notre planète comme des goinfres, qu’on pollue à foison et qu’on laisse des dirigeants voter des lois liberticides.

Bonnes pratiques

Bien que je suis clairement en faveur de mettre un frein à l’enthousiasme des programmeurs FP tant qu’un terrain d’entente intelligent ne sera pas trouvé, ça ne veut pas dire qu’on ne peut pas bénéficier de leurs propositions.

En effet, sachez que vous serez un meilleur programmeur, et que votre programme gagnera en qualité, si vous appliquez quelques principes de programmation fonctionnelle et utilisez les outils déjà existant en Python pour en tirer parti.

Le tout est de ne pas en abuser, et surtout, de ne pas s’interdire de pondre un code bête et simple quand c’est plus rapide ou plus clair.

D’abord, limiter les effets de bord est toujours une bonne chose.

Si vous vous trouvez à modifier une structure de données dans une fonction alors qu’elle l’a reçue en paramètre, réfléchissez à deux fois. Est-ce vraiment nécessaire ? Ne pouvez-vous pas faire autrement. A part pour quelques cas très précis (tri sur place, passes multiples, cache, etc), il rare d’avoir à le faire.

Utilisez les intensions et les générateurs. Utilisez itertools. Utilisez functools. Ces outils demandent un peu d’apprentissage, mais ils sont formidables : plus performants, plus expressifs, plus lisibles.

Il vous faudra un peu de pratique pour trouver un bon équilibre. Arrive un point où on les utilise partout, ce qui rend le debuggage plus difficile. Passé ce stade, revenez en arrière, calmez-vous, respirez, retourner à l’impératif, ce n’est pas non plus retourner au visual basic.

Quand vous faites de la programmation concurrente, voyez si vous ne pouvez pas isoler vos états derrière des services, et faites communiquer les services entre eux via des messages immutables. Votre application sera plus claire, plus stable.

D’une manière générale, il est bon de diviser son code en petites unités, fonctionnelles justement, qui se passent des valeurs les unes aux autres.

Mais au final, n’oubliez pas la définition d’un bon code :

Le reste n’est que drosophilie sodomite.

Point rapide sur les types hints 6

lundi 27 avril 2015 à 11:57

Avec la 3.5 arrive les types hints.

Pour remettre ça dans le contexte, Python 3 avait introduit les functions annotations, c’est à dire la possibilité de rajouter une information aux paramètres et valeur de retour d’une fonction. Cette information pouvait être n’importe quel objet Python :

def add(a: "n'importe quel objet", b: 1) -> ImportError:
    return a + b

Ces annotations ne faisaient absolument rien à part être accessibles :

>>> add(1, 2)
3
>>> add.__annotations__
{'b': 1, 'a': "n'importe quel objet", 'return': <class 'ImportError'>}

Le but étant de permettre à la communauté de faire des propositions sur leur utilisation jusqu’à ce que la préférée soit retenue.

Aucune proposition n’a néanmoins autant de succès que de permettre optionnellement de déclarer le typage, et c’est donc ce processus qui est amorcé avec la prochaine version où l’on pourra faire :

def add(a: int, b: int) -> int:
    return a + b

Pour les cas plus complexes, un module contient des interfaces courantes. Par exemple, si votre fonction fait la somme d’un itérable :

from typing import Iterable
 
def total(l: Iterable):

Si vous voulez limiter l’itérable à un itérable d’entiers :

def total(l: Iterable[int]):

Pour les fonctions très complexes, ça peut vite devenir très, très moche :

def truc(l: Iterable[Tuple(Any(Bidule, Machin))], oui=True: bool, **kwargs) -> FuckIt:
    return FuckIt(l, oui, **kwargs)

Par ailleurs, le module typing va devenir une des rares bonnes raisons d’utiliser import * :)

La spec prévoit qu’on puisse mettre ces annotations dans un fichier séparé du code avec juste la signature des fonctions. D’un côté, ça a le bénéfice de permettre d’annoter un code inaltérable, ou en Python 2.7. D’un autre, le DRY va du coup aller se faire foutre et franchement, je code pas en Python pour avoir des headers comme en C.

L’implémentation originale ne fera strictement rien. Elle est juste là pour voir ce que les outils vont en faire : un IDE pourra facilement trouver une erreur de type, on pourra faire des hooks git qui vérifient qu’on n’a pas niqué notre code en balançant un mauvais type et pypy/cython/nuikta/numba vont pouvoir se rouler dans la boue des optimisations.

Cela veut dire que si j’ignore mon IDE et que je passe des arguments d’un type qui n’est pas annoté, Python l’exécutera quand même. Ouf.

Le bilan ?

Globalement positif.

C’est vrai que c’est méga moche. Et les propositions alternatives ne sont pas mieux.

Je rêve que les annotations dans les docstrings soient utilisées, mais Guido a dit niet pour des raisons que je trouve faiblement fondées : les dosctrings sont un format libre dur à parser et alors que la syntaxe Python est structurée, avec un bon lexer. L’argument avancé dans le PEP est carrément faible : les docstrings ça se fold dans tous les éditeurs, c’est tout l’avantage.

Perso, je pense que normaliser enfin le format des docstrings ne me parait pas une mauvaise chose. Et puis si on arrive à parser le HTML pourri qu’on trouve sur le Web, 3 lignes de docstrings, c’est pas la mer à boire.

Néanmoins c’est optionnel, souple, et la première implémentation ne se mouille pas trop, ce qui laisse de la marge pour corriger les problèmes qui vont forcément venir.

Le bénéfice potentiel est énorme.

D’un côté, on aura toujours un langage facile à apprendre et enseigner, le duck typing, l’exploration de code, le typage dynamique par défaut.

De l’autre, quand un surcroit de rigueur sera nécessaire, on pourra rajouter un filet de sécurité. Et dans quelques temps, peut-être même gagner sérieusement en perf.

Mais quand même, putain c’est moche.

Error happened! 0 - count(): Argument #1 ($value) must be of type Countable|array, null given In: /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php:428 http://ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/?71 #0 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(999): VroumVroum_Blog->update() #1 /var/www/ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/index.php(1): require_once('...') #2 {main}