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...

La branlette fait faire des économies 26

samedi 6 juin 2015 à 15:16

Vous connaissez cette sagesse populaire qui incite à ne jamais faire les courses le ventre vide ?

J’aimerais ajouter une règle d’hygiène de vie qu’il vaudrait enseigner dès le collège à tous les mâles en devenir : branlez-vous avant l’action.

Par action je n’entends pas la session de pirouette basque avec la voisine, bien que selon la situation ça puisse être une bonne stratégie.

Non, je parles de remplir un objectif qui demande un minimum de prise de décision suivi d’une action ou d’une interaction sociale majeure.

Dans Le Loup de Wall Street, qui n’est pas un monument du cinéma mais reste un divertissement scorcesien décent, un personnage secondaire résume bien le principe :

Et la raison est que, en plus de faire diminuer les tensions, se palucher un coup améliore sérieusement la capacité de choix, en retirant temporairement du diagramme décisionnel de votre tête tout un tas de losanges jaunes, biologiques et psychologiques.

Évidement, certains pourraient répliquer qu’il faudrait mieux avoir du self control, du recul et la capacité à gérer les situations en dehors de sa zone de confort.

A cela je répliquerais que oui, mais en attendant que chacun fasse murir le Clint Eastwood qui est en lui, un paquet de kleenex propose un bon rapport qualité/prix.

Avant un entretien de job, branlez-vous.

Avant de faire un gros achat, branlez-vous.

Avant d’aller dans une intense réunion, branlez-vous.

Avant de faire une code review, branlez-vous.

Avant de valider une mise en prod, branlez-vous.

Le seul cas où je ne recommande pas de se masturber, c’est pour la drague, contrairement à ce qu’on dit dans Marie à tout prix :

You choke the chicken before any big date, don’t you? Tell me you spank the monkey before any big date. Oh my God, he doesn’t flog the dolphin before a big date. Are you crazy? That’s like going out there with a loaded gun! Of course that’s why you’re nervous. Oh my dear friend, please sit, please. Look, um, after you’ve had sex with a girl, and you’re lying in bed with her, are you nervous? No, you’re not, why?

Parce que conserver une érection en suspens, ça garde motivé pour tendre vers son but, si je puis dire.

Pour le reste, dans le doute, branlez-vous.

Il en restera toujours quelque chose, n’est-pas ?

Hasta la vista ! 4

mardi 19 mai 2015 à 09:45

Je me barre pour environ 2 semaines, et ne pondrai pas d’article pendant cette période.

Mettez ce temps à profit pour travailler votre lancer de nain.

Un gros guide bien gras sur les tests unitaires en Python, partie 5 7

dimanche 17 mai 2015 à 09:35

Vous avez vu les modules pour faire les tests, mais dès que vous allez vouloir faire des tests sérieux, vous allez vous heurter à la dure réalité.

La réalité est que pour tester, il vous faut la réalité.

Par exemple, si vous tapez dans une base de données, il vous faut une base de données opérationnelle. Pour tester un téléchargement, il vous faut une connexion internet. Pour tester si votre API fonctionne, il faut lancer un serveur Web.

Autre chose, si votre code appelle un autre code, comment vous assurer que cet appel a bien eu lieu ? Il faudrait aussi un logger pour tous les appels, et si le code n’est pas le vôtre, c’est encore plus chiant.

Comme nous savons que les informaticiens sont des grosses larves, il y a forcément une solution, au moins partielle, à ces problèmes. En l’occurrence, on va jouer au docteur, à la dinette, aux cowboys et aux indiens.

Bref, on va jouer à faire semblant.

Les objets mocks

Un objet mock, c’est un objet basé sur le null object pattern qui sert à faire semblant. Quand on l’instancie avec n’importe quoi, ça marche, quand on appelle n’importe quelle méthode, ça marche et ça renvoie un mock.

Bien entendu, comme les besoins des tests sont un peu plus raffinés que ça, mock fait plus que du null object pattern, et permet :

Alors évidement, comme ça, je me doute bien que la puissance de l’outil ne vous frappe pas en face comme le nez au milieu de l’eureka dans un couloir.

C’est pour ça qu’on va passer aux exemples concrets. D’abord, assurez-vous de pouvoir faire import unittest.mock, qui est dispo depuis Python 3.3. Si ce n’est pas le cas, l’installer avec pip install mock vous permettra de l’importer sous la forme import mock. Le reste, c’est pareil.

On dirait que moi je t’attaque et toi tu meurs pas

Un objet mock est un callable, c’est-à-dire qu’il peut être appelé comme une fonction ou une classe, et il retourne toujours un objet mock :

>>> from unittest.mock import MagicMock # ou from mock import MagicMock
>>> mock = MagicMock()
>>> print(mock)
<MagicMock id='140302100559296'>
>>> mock()
<MagicMock name='mock()' id='140302101821704'>
>>> mock(1, True, [Exception, {}])
<MagicMock name='mock()' id='140302101821704'>

On peut appeler n’importe quoi sur son objet mock, et ça retourne toujours un objet mock :

>>> mock.foo()
<MagicMock name='mock.foo()' id='140302101723960'>
>>> mock.nimporte().nawak().je().te().dis()
<MagicMock name='mock.nimporte().nawak().je().te().dis()' id='140302101825744'>
>>> mock + mock - 10000
<MagicMock name='mock.__add__().__sub__()' id='140302134081520'>

Quand retourner un objet mock n’est pas possible, l’objet essaye d’avoir le comportement qui fera planter le moins possible :

>>> int(mock)
1
>>> [m for m in mock]
[]

On dirait que le bâton, là, c’est un sabre laser

Parfois, néanmoins, il est utile de vouloir avoir un comportement spécifique. Il se trouve que les méthodes des objets mocks peuvent être des objets mocks. Mock, mock, mock !!!!!

Et les objets mocks peuvent être configurés pour avoir un effet de bord ou une valeur de retour :

mock.you = MagicMock(side_effect=ValueError('mofo !')) # un callable marche aussi
>>> mock.you()
Traceback (most recent call last):
  File "<ipython-input-21-a7e6455585e9>", line 1, in <module>
    mock.you()
  File "/usr/lib/python3.4/unittest/mock.py", line 885, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/usr/lib/python3.4/unittest/mock.py", line 941, in _mock_call
    raise effect
ValueError: mofo !
>>> mock.mock = MagicMock(return_value="moooooooooock")
>>> mock.mock()
'moooooooooock'

Cela vous permet d’utiliser les objets mocks comme des remplacements pour des objets réels dans vos tests mais chiants à instancier comme une event loop, un serveur, une connexion à une base de données… Ca permet aussi de remplacer des appels très longs par des trucs instantanés.

Mais la partie vraiment fun, c’est qu’on peut associer des vrais objets avec des objets mocks :

>>> class VraiObjetSerieuxEtTout:
...     def faire_un_truc_super_serieux(self):
...         return "... and don't call me Shirley"
...     def faire_un_autre_truc_serieux(self):
...         return "why so serious ?"
...
>>> sirius = VraiObjetSerieuxEtTout()
>>> sirius.faire_un_truc_super_serieux = MagicMock() # It's a kinda magic, magic !
>>> sirius.faire_un_autre_truc_serieux()
'why so serious ?'
>>> sirius.faire_un_truc_super_serieux('ieux').delamort()[3:14] + [1, 2]
<MagicMock name='mock().delamort().__getitem__().__add__()' id='140302103288296'>

Et là ça devient super sympa : vous pouvez utilisez vos vrais objets, et pour certains appels, juste vous faciliter la vie pour les tests.

On dirait qu’on compte le nombre de balles que tu as tirées

Puisque les objets mocks sont un peu les grosses salopes de la programmation et acceptent tout ce qui vient (oups, je viens de tuer l’ambiance métaphore enfantine là), il peut être nécessaire de vérifier ce qui s’est passé. Or il se trouve qu’ils intègrent un historique des appels :

>>> sirius.faire_un_truc_super_serieux.mock_calls
[call('ieux'),
 call().delamort(),
 call().delamort().__getitem__(slice(3, 14, None)),
 call().delamort().__getitem__().__add__([1, 2])]

Et comme vérifier qu’un appel a bien eu lieu est une tâche courante, des méthodes pour les tests unitaires ont été intégrées :

>>> sirius.faire_un_truc_super_serieux.assert_called_with('ieux')
>>> sirius.faire_un_truc_super_serieux.assert_called_with('not_ieux')
Traceback (most recent call last):
  File "<ipython-input-56-e8c4890f08d9>", line 1, in <module>
    sirius.faire_un_truc_super_serieux.assert_called_with('not_ieux')
  File "/usr/lib/python3.4/unittest/mock.py", line 760, in assert_called_with
    raise AssertionError(_error_message()) from cause
AssertionError: Expected call: mock('not_ieux')
Actual call: mock('ieux')

On dirait que tu vas mettre ces porte-jartelles et…

Pour finir, le module mock vient avec patch(), qui sert à, surprise, patcher les objets, et propose des context managers et des décorateurs pour se faciliter la vie.

Par exemple, détourner open() temporairement :

>>> from unittest.mock import patch
>>> with patch('__main__.open', mock_open(read_data='wololo'), create=True) as mock:
...     with open('zefile') as h:
...         result = h.read()
...
>>> mock.assert_called_once_with('zefile')
>>> assert result == 'wololo'

Ou alors avoir une partie d’un module qui soit un mock pour tout un appel de fonction :

@patch('os.listdir')
def ah(mock):
    import os
    print(os.listdir('.'))
    # l'objet mock initial est aussi passé en param automatiquement
    print(mock)
ah()
## <MagicMock name='listdir()' id='140302096346864'>
## <MagicMock name='listdir' id='140302101454688'>

Le module mock est vraiment très complet, avec des outils pour checker les signatures, passer isinstance(), overrider le contenu d’un dico, et tout un tas de cas particuliers et corner cases. Donc lisez la doc si vous rencontrez un blocage avant de paniquer.

Dis, comment on fait les bébés

Exemple prit d’une base de code IRL, avec une fonction pytest qui teste un objet response représentant une réponse HTTP. Si on appelle write() sur cet objet sous-jacent elle doit faire des appels à deux méthodes privées et une méthode d’un objet Twisted.

Problème, ces méthodes :

Du coup, on les remplace par des objets mocks, et yala :

def test_write(response):
    assert response.write != response._req.write
    response._disable_rendering = MagicMock(name='_disable_rendering')
    response._set_twisted_headers = MagicMock(name='_set_twisted_headers')
    response.write(b'test')
    response._set_twisted_headers.assert_called_once_with()
    response._disable_rendering.assert_called_once_with()
    assert response.write == response._req.write
    response._req.write.assert_called_once_with(b'test')

Et pourquoi ? Et pourquoi ? Et pourquoi ?

Prochaines étapes, savoir quand tester, et quoi tester, mais aussi comment rendre un code plus testable. Probablement la partie qui sera la plus difficile à écrire pour moi, car c’est assez subjectif. On parlera sans doute du code coverage, et je gage que je vais devoir créer un petit projet bidon pour tester tout ça, du genre un minifieur d’URL ou autre. Faudra voir l’inspiration.

Pourquoi il est difficile de “juste ajouter un nouveau mot clé” 11

samedi 16 mai 2015 à 09:52

Avec les types hints, beaucoup ont suggéré l’ajout de nouveaux mots clés pour déclarer les types des paramètres et des variables. Guido a refusé, au moins pour les paramètres des fonctions, et s’en tient aux annotations.

Mais pourquoi être si têtu ? Après tout, c’est forward compatible, n’est-ce pas ?

Faux.

Quand on rajoute un mot clé, on ne peut plus l’utiliser comme nom de variable, et donc tout code qui utilise une variable qui porte ce nom va planter quand il passera à cette version de Python.

Inutile de dire qu’un mot clé doit être court et explicite, et que tous les mots courts et explicites sont utilisés comme noms de variables quelque part.

C’est pour cette raison que l’ajout de async/await a lancé toute une discussion sur la manière de changer la grammaire.

Et vous savez ce qui a été décidé ?

De ne PAS interdire de faire :

async = await = True
async def foo():
    await bar
# pas de SyntaxError

En clair, le parseur va détecter des cas particuliers pour ces instructions dans lesquelles il les identifie comme mot clés, et autoriser quand même leur usage comme variable dans tous les autres cas.

C’est un compromis pour garder la compatibilité ascendante, mais un compromis qui non seulement introduit un cas particulier dans le code du parseur (beurk), mais en plus permet d’écrire un code qui avant n’était pas permis, rendant le langage un peu moins cohérent. Un changement que maintenant on va se trainer pour les releases à venir, qui ouvre un précédent pour les futures débats, qui peut potentiellement s’accumuler avec d’autres changement futurs…

Faire évoluer un langage qui a 20 ans et dont il y a des millions de lignes de code partout, c’est dur. Chaque petite modification peut entrainer des conséquences importantes, et tout juste milieu renferme des craquelures d’inélégance.

C’est aussi pour ça qu’il est si plaisant d’inventer son langage, son framework, son outil pour faire x mieux que le status quo y. Il est parfait. Il est tout frais. Personne ne l’utilise. Il n’a encore eu aucune cicatrice.

A l’inverse, garder le cap malgré les remous, maintenir une diète saine et un équilibre mais accepter les saloperies qui passent de temps en temps, ça c’est difficile. Je suis épaté que Python soit resté si propre avec tout ce qui lui est arrivé au fil des années, et c’est grâce Guido qui a su dire NON à tout un tas de petits détails qui ne paraissaient pas importants, mais qui cumulés sur 2 décennies auraient transformé le langage en créature fantasque.

Pourvu qu’ça dure.

Un ptit post pour remercier tous les contributeurs 32

vendredi 15 mai 2015 à 21:50

Après ces quelques années de blogging je voulais remercier, et je pense pouvoir parler aussi au nom de Sam, tous ceux qui ont participé à la joie et la bonne humeur de ce blog.

Mais aussi à l’évolution d’IndexError où nous pouvons voir chaque jour avec un très grand plaisir des contributeurs aider sans critiquer ou juger, apporter des réponses ou donner des pistes sans rien demander en échange.

Merci à vous tous de donner de votre (précieux) temps pour aider votre “prochain” programmeur. La vitesse des réponses sur IndexError est tout juste hallucinante malgré la “petite” communauté.

De plus les commentaires sur le Blog sont toujours courtois et intéressants.

Je remercie en particulier pour IndexError, pour le Blog ça serait trop long à lister (mais j’en oublie certainement):

Foxmask, boblinux, jc, cOda, doublenain, furankun, Hawke et bien d’autres…

Aller je vais aux putes, ce post m’a épuisé.

Passez un bon Noël!

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/?69 #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}