PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Des années plus tard, je n’aime toujours pas les CBV

mardi 21 mai 2013 à 10:52

Les Class Based Views se trouvent maintenant partout en Django. Je les comprends aujourd’hui très bien, et les utilise souvent au mieux. Dernièrement j’ai dû utiliser django-rest-framework, une très belle application, qui fait un usage utile et massif des CBV, et effectivement, cette architecture m’a rendu très productif.

Mais je maintiens ce que je dis depuis le début : ce n’est pas un pas en avant.

D’abord, qui dit CBV dit référence constante à la documentation. J’ai été productif avec CBV parce que :

Mais ce n’est pas comme ça que c’est censé être. Si vous utilisez une belle API, comme requests ou path.py, vous n’avez PAS besoin de tout cela. Vous jetez un coup d’œil à la doc, et vous faites 80% du boulot dans le shell, puis de temps à autre, vous vous référez à la documentation sur les points ambiguës. Je n’ai jamais du aller dans le code source de requests, je n’ai jamais eu besoin de comprendre urllib de fond en comble pour utiliser requests, et je n’ai pas besoin de connaître les subtilités de plusieurs paradigmes de programmation pour utiliser requests.

Alors oui, requests ne peut en aucun cas être comparé à Django en terme de taille et de couverture de besoin, et certes, en tant que professionnel averti, je suis tenu d’être capable de faire tout cela. Mais il y a quand même une énorme différence d’effort à fournir, et de qualification à avoir, pour l’usage de cette techno.

En prime, il est TRÈS difficile de débugger une CBV :

Rajoutez à cela que la syntaxe est verbeuse comme c’est pas permis :

def get_context_data(self, *args, **kwargs):
    context = super(MaClass, self).get_context_data(*args, **kwargs)
    context['ma_var'] = "ma valeur"
    return context

Pour rajouter UNE variable dans le contexte ? Rhaaaaaaaaaaa. Je code en Python, pas en Java !

Et terminez sur le fait que pour utiliser tous les décorateurs codés depuis 5 ans, vous avez une syntaxe bien pourrie ou alors des réimplementations complètes sous forme de mixins. Yeah !

Vous comprendrez que ça me gonfle.

Tout ça pour quel gain ? Oui on gagne un peu en flexibilité, mais ça ne compense pas tout le reste, et ça ne permet en rien de faire des choses qu’on ne pouvait pas faire avant.

Je n’ai pas vu encore la moindre utilisation des CBV qui me fasse dire, “putain c’est tellement mieux qu’avant pour cette partie”. Ni aucune lib qui me fasse dire : “ouais, on aurait pas pu le faire aussi bien avec des vues sous forme de fonctions”.

Avec les CBV devenant la manière par défaut de coder les vues en Django, le framework devient beaucoup moins accessible au débutant, alors que sa facilité d’usage était un de ses points forts.

Bref, je suis de l’avis de Nick Coghlan, les CBV ne sont pas une erreur, elles ont leur utilité, mais balancer à la poubelle les vues sous forme de fonction est une vaste connerie.

flattr this!

Il faut coucher utile

lundi 20 mai 2013 à 10:10

Petit transcript, parce que j’adore rappeler que tous les avis sont dans la nature.

Je déjeune avec une très ancienne amie d’enfance, chanteuse lyrique de métier, glamour, voir cul-cul, bobo bio récupérant ses meubles dans les poubelles, amatrice de chats, d’une grande gentillesse, le sourire tendre servi avec des tâches de rousseur. Bref, pas la Paris Hilton de base.

Sam : “En fait tu considères que ta carrière décolle vraiment quand ? Quand tu n’as plus besoin de faire d’auditions ?”

Coraline : “Plutôt quand tu es payée ce que tu dois être payée pour ton travail. Les auditions, ça n’a rien à voir. Moi je galère toujours. Tu te souviens de mon amie Pascaline ? Celle qui a couché avec < Insérer ici le nom d'un grand ténor >. Dans la foulée elle a eu 3 auditions avec 3 des plus grandes maisons de Paris. Il faudrait que j’apprenne à faire comme elle.”

“Au moins vous les filles, vous pouvez faire ça. Nous on peut même pas coucher pour réussir.” (Sauf avec les gays, mais j’y ai pas pensé sur le coup. Remarque je devrais peut être tenter la cougar.)

“Attend, c’est le minimum. Déjà elle couche avec lui, c’est normal qu’il fasse quelque chose pour elle. Elle a pu se faire écouter par une agence qui m’a répondu qu’ils n’avaient plus le temps d’écouter personne. Comme ça, sur un coup de fil du mec. Moi je suis pour, hein. Si le mec te plaît pas, non, mais si il te plaît, y a pas de raison. J’avais un prof de chant qui me disait ‘Il faut coucher utile. Agréable, mais utile.’”

FEMA, où es-tu ?

flattr this!

Nouvelle config iPython

dimanche 19 mai 2013 à 19:24

J’ai bricolé une config pour iPython dernièrement. Rappelez-vous, on peut complètement customiser ce shell.

Voici ce que j’ai dans mon ./.config/ipython/profile_default/ipython_config.py:

c.TerminalIPythonApp.exec_lines = [
'doctest_mode on',
'from __future__ import unicode_literals, absolute_import, division',
'import sys',
'import os',
'import re',
'import json',
'import base64',
'import calendar',
'import csv',
'import datetime',
'import itertools',
'import random',
'import hashlib',
'import tempfile',
'import argparse',
'import math',
'import random',
'import subprocess',
'from glob import glob',
'from uuid import uuid4',
'from datetime import datetime, timedelta',
'from collections import Counter, OrderedDict',
"""
def initial_imports():
 
    # ne faites pas ça dans un code de prod :-)
    global path, relativedelta, requests 
 
    print("\\nImported : 'sys', 'os', 're', 'json', 'base64', 'calendar', 'csv', 'datetime', 'itertools', 'random', 'hashlib', 'tempfile', 'argparse', 'math', 'random', 'glob', 'uuid4', 'datetime', 'timedelta', 'Counter', 'OrderedDict', 'subprocess'\\n")
 
    try:
        from path import path
        print('Imported : "path"')
    except ImportError:
        print("'path' is not available")
 
    try:
        from dateutil import relativedelta
        print('Imported : "relativedelta"')
    except ImportError:
        print("'dateutil' is not available")
 
    try:
        import requests
        print('Imported : "requests"')
    except ImportError:
        print("'requests' is not available")
 
    try:
        env = os.environ['VIRTUAL_ENV']
        print("\\nENV '{}'. You can import:\\n".format(os.path.basename(env)))
        cmd = subprocess.check_output([env + "/bin/pip", "freeze"],
                                      stderr=subprocess.STDOUT).strip().split("\\n")
        p = re.compile(r'(^.*\:\s)|((#|@).*$)|(==.*$)')
        print("'" + "', '".join(sorted(set(os.path.basename(p.sub('', f)) for f in cmd))) + "'")
    except KeyError:
        pass
 
    print('')
""",
"initial_imports()"
]

Du coup, au démarrage d’iPython j’ai les bénéfices suivant :

Je vire aussi la bannière de démarrage :

c.TerminalIPythonApp.display_banner = False

Et hop, mon affichage au démarrage ressemble à ça :

$ ipython
Exception reporting mode: Plain
Doctest mode is: ON

Imported : 'sys', 'os', 're', 'json', 'base64', 'calendar', 'csv', 'datetime', 'itertools', 'random', 'hashlib', 'tempfile', 'argparse', 'math', 'random', 'glob', 'uuid4', 'datetime', 'timedelta', 'Counter', 'OrderedDict', 'subprocess'

'path' is not available
'dateutil' is not available
Imported : "requests"

ENV 'my_env'. You can import:

'BeautifulSoup', 'Django', 'Fabric', 'Jinja2', 'Pillow', 'Pygments', 'Sphinx', 'URLObject', 'Werkzeug', 'argparse', 'beautifulsoup4', 'colorlog', 'distribute', 'django-appconf', 'django-bootstrap-pagination', 'django-bootstrap-toolkit', 'django-compressor', 'django-debug-toolbar', 'django-extensions', 'django-guardian', 'django-loginas', 'django-model-utils', 'django-pdb', 'django-pytest', 'django-rest-framework.git', 'django-template-repl', 'docutils', 'easy-thumbnails', 'feedparser', 'gunicor

Ça fait un gros bloc visuellement, mais ça ne s’affiche qu’une fois, et le délai est acceptable.

Si j’ai besoin de l’info à nouveau, je peux toujours appeler :

initial_imports()

Que ce genre de choses soient possibles et faciles me font adorer Python. La force du langage, ce n’est pas seulement son core, c’est tout son environnement, ses libs, ses outils qui gravitent autour qui rendent l’expérience de dev fantastique.

flattr this!

Qu’est-ce qu’un callable en Python ?

samedi 18 mai 2013 à 09:05

Petit définition du jour.

Un callable, qu’on peut traduire maladroitement par “appelable”, est un objet qui peut être appelé. Et ça ne vous arrange pas beaucoup de savoir ça.

On parle de callable dans les tutos, les docs, etc, en supposant que vous savez ce que ça veut dire.

En fait c’est très simple, si vous pouvez mettre deux parenthèses après le nom d’un objet (en fait de la variable qui contient l’objet, mais ne chipotons pas) pour obtenir un effet, alors l’objet est un callable.

Une fonction est un callable:

>>> def fonction():
...     print("Vous m'avez appelé")
...
>>> fonction() # appel de la fonction
Vous m'avez appelé

Une classe est un callable:

>>> Class() # appel de la classe
<__main__.Class object at 0x26d8310>

Une méthode est un callable :

>>> class Class(object):
def methode(self):
print("Veuillez patientez, nous allons répondre à votre appel")
...
>>> obj = Class()
>>> obj.methode() # appel de la méthode
Veuillez patientez, nous allons répondre à votre appel

Il sont callables car on peut faire () dessus, et il se passe quelque chose. On peut les appeler.

Et par voie de conséquence, un callable peut retourner une valeur quand il est appelé. En fait, un callable est même garanti de retourner une valeur quand il est appelé, puisque toute fonction ou méthode retourne None par défaut en Python, et toute classe retourne au moins un objet vide.

Les string, les int, les listes, les fichiers ou les dicos ne sont pas des callables :

>>> 1()
Traceback (most recent call last):
File "<ipython-input-11-769e123a5ede>", line 1, in <module>
1()
TypeError: 'int' object is not callable
 
>>> ""()
Traceback (most recent call last):
File "<ipython-input-12-de235e3e26d7>", line 1, in <module>
""()
TypeError: 'unicode' object is not callable
 
>>> []()
Traceback (most recent call last):
File "<ipython-input-13-bda5b10b5bf7>", line 1, in <module>
[]()
TypeError: 'list' object is not callable
 
>>> {}()
Traceback (most recent call last):
File "<ipython-input-14-67901a4bc3e8>", line 1, in <module>
{}()
TypeError: 'dict' object is not callable
 
>>> f = open('/etc/fstab')
>>> f()
Traceback (most recent call last):
File "<ipython-input-16-0ec059b9bfe1>", line 1, in <module>
f()
TypeError: 'file' object is not callable

Mais les classes (et oui, ce sont des classes, pas des fonctions malgré leurs noms en minuscule) str, int, list, dict et file sont des callables :

>>> int()
0
>>> str()
''
>>> dict()
{}
>>> list()
[]
>>> file('/etc/fstab')
<open file u'/etc/fstab', mode 'r' at 0x2689930>

En fait, la plupart des objets ne sont tout simplement pas des callables. Aucun nouvel objet n’est un callable par nature.

>>> obj = Class()
 
>>> obj()
Traceback (most recent call last):
File "<ipython-input-18-da8742d08647>", line 1, in <module>
obj()
TypeError: 'Class' object is not callable

Donc, quand on vous dit de passer un “callable” en paramètre, généralement on vous dit de passer une fonction (sans les parenthèses), une classe (sans les parenthèses) ou une méthode (sans les parenthèses), afin de s’en servir comme callback.

Par exemple, les champs des modèles Django acceptent un callable comme paramètre par défaut. Comme les callables sont garantis de retourner une valeur, Django utilise cela pour créer dynamiquement une valeur par défaut. La fonction sort() des listes accepte un callable en callback pour le paramètre key.

Demander un callable, c’est donc demander un truc qu’on peut appeler en utilisant des parenthèses, et qui va retourner un autre truc.

Au passage, pour votre culture, on peut créer ses propres callables. Ça sert rarement, dans quelques cas tordus. Il suffit de créer une classe avec la méthode magique __call__, et l’objet retourné sera automatiquement un callable :

>>> class CreerUnCallable(object):
...         def __call__(self):
...                 return "Vous m'avez appelé, maître ?"
...
>>> un_callable = CreerUnCallable()
>>> print(un_callable())
Vous m'avez appelé, maître ?

On a donc ici DEUX callables : la classe, qui est callable par nature, et l’instance de la classe, qui est callable grâce à la méthode __call__.

Et moi j’ai commencé à taper cet article parce que je me suis dit que c’était une notion simple et que ça allait être vite fait, juste avant le petit dèj. Ca fait une heure que je suis dessus. Qu’est-ce que je suis con des fois.

flattr this!

Mise à jour

samedi 18 mai 2013 à 00:49

J’ai mis à jour la liste des cours en Python et Django.

J’ai aussi updaté la copie offline du site pour que vous puissiez en profiter dans le train, au camping, chez mamie, etc.

Enjoy.

flattr this!