Site original : Sam & Max: Python, Django, Git et du cul
Ceci est un post invité de Foxmask posté sous licence creative common 3.0 unported.
Préambule
Je sais bien qu’une partie de ce billet ne plaira pas à Sam&Max (thanks to the Django CBV & Mixin:)
Introduction
La première partie va planter le décors en commençant par vous montrer comment s’articule une application avec formulaire composé d’un sous formulaire en sus (j’expliquerai pourquoi après :)
Pour ce faire, je vous emmène dans l’univers du 7° art, viendez on va refaire StarWars!
Un modèle, un formulaire, une vue, un template et ca sera fini.
le models.py
from django.db import models class Movie(models.Model): """ Movie """ name = models.CharField(max_length=200, unique=True) description = models.CharField(max_length=200) def __str__(self): return "%s" % self.name class Episode(models.Model): """ Episode - for Trilogy and So on ;) """ name = models.CharField(max_length=200) scenario = models.TextField() movie = models.ForeignKey(Movie) def __str__(self): return "%s" % self.name |
le forms.py, tout rikiki :
from django import forms from django.forms.models import inlineformset_factory from starwars.models import Movie, Episode class MovieForm(forms.ModelForm): class Meta: """ As I have to use : "exclude" or "fields" As I'm very lazy, I dont want to fill the list in the "fields" so I say that I just want to exclude ... nothing :P """ model = Movie exclude = [] # a formset based on the model of the Mother "Movie" and Child "Episode" + 1 new empty lines # for more details, have a look at https://docs.djangoproject.com/fr/1.9/topics/forms/modelforms/#inline-formsets EpisodeFormSet = inlineformset_factory(Movie, Episode, fields=('name', 'scenario'), extra=1) |
la vue views.py, très sèche, très DRY ;)
from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.views.generic import CreateView, UpdateView, ListView from starwars.models import Movie from starwars.forms import MovieForm, EpisodeFormSet class MovieMixin(object): model = Movie form_class = MovieForm def get_context_data(self, **kw): """ init form with data if any """ context = super(MovieMixin, self).get_context_data(**kw) if self.request.POST: context['episode_form'] = EpisodeFormSet(self.request.POST) else: context['episode_form'] = EpisodeFormSet(instance=self.object) return context def get_success_url(self): """ where to go back, once data are validated """ return reverse("home") def form_valid(self, form): """ form validation """ formset = EpisodeFormSet((self.request.POST or None), instance=self.object) if formset.is_valid(): self.object = form.save() formset.instance = self.object formset.save() return HttpResponseRedirect(reverse('home')) class Movies(ListView): model = Movie context_object_name = "movies" template_name = "base.html" class MovieCreate(MovieMixin, CreateView): """ MovieMixin manages everything for me ... """ pass class MovieUpdate(MovieMixin, UpdateView): """ ... and as I'm DRY I wont repeat myself myself myself ;) """ pass |
Pour finir de planter le décors et les costumes (merci Roger Hart et Donald Cardwell)
le template base.html
<!DOCTYPE html> <html lang="fr"> <head> <title>Manage stories for StarWars</title> </head> <body> <h1>Stories Manager for Starwars</h1> {% block content %} <a href="{% url 'movie_create' %}">Add a movie</a><br/> <h2>Movie list</h2> <ul> {% for movie in movies %} <li><a href="{% url 'movie_edit' movie.id %}">{{ movie.name }}</a></li> {% endfor %} </ul> {% endblock %} </body> </html> |
enfin movie_form.html (le template utilisé par les UpdateView & CreateView)
{% extends "base.html" %} {% block content %} <form method="post" action=""> {% csrf_token %} {{ formset.management_form }} <table> {{ form.as_table }} </table> <table> {{ episode_form.as_table }} </table> <button>Save</button> </form> {% endblock %} |
Mise à jour de la base de données
cela s’impose :
(starwars) foxmask@foxmask:~/DjangoVirtualEnv/starwars/starwars $ ./manage.py migrate Operations to perform: Synchronize unmigrated apps: messages, starwars, staticfiles Apply all migrations: contenttypes, admin, sessions, auth Synchronizing apps without migrations: Creating tables... Creating table starwars_movie Creating table starwars_episode Running deferred SQL... Installing custom SQL... |
Voilà le tout est prêt (après le lancement du serveur bien sûr), je peux allégrement créer ma double trilogie pépère tel George Lucas.
Trackons l’impie
Seulement un jour arrive où, moi, George Lucas, je vends StarWars à Walt Disney, mais comme je ne veux pas rater de ce qu’ils vont faire de mon “bébé”, je rajoute un “tracker de modifications” à mon application, pour ne pas perdre le “field” de l’Histoire.
Installation de Tracking Fields
en prérequis django-tracking-fields requiert django-cuser, donc ze pip qui va bien donne :
(starwars) foxmask@foxmask:~/DjangoVirtualEnv/starwars/starwars $ pip install django-tracking-fields django-cuser Collecting django-tracking-fields Downloading django-tracking-fields-1.0.6.tar.gz (58kB) 100% |████████████████████████████████| 61kB 104kB/s Collecting django-cuser Downloading django-cuser-2014.9.28.tar.gz Requirement already satisfied (use --upgrade to upgrade): Django>=1.5 in /home/foxmask/DjangoVirtualEnv/starwars/lib/python3.5/site-packages (from django-cuser) Installing collected packages: django-tracking-fields, django-cuser Running setup.py install for django-tracking-fields ... done Running setup.py install for django-cuser ... done Successfully installed django-cuser-2014.9.28 django-tracking-fields-1.0.6 |
comme de coutume, après un pip install
, une modification dans le settings.py
suit,
INSTALLED_APPS = ( ... 'cuser', 'tracking_fields', ... ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', 'cuser.middleware.CuserMiddleware', ## <=== ne pas oublier, pour chopper le user qui fait le con avec mes films;) ) |
le petit migrate qui va bien aussi pour ajouter les tables pour les modèles de django-tracking-fields
(starwars) foxmask@foxmask:~/DjangoVirtualEnv/starwars/starwars $ ./manage.py migrate Operations to perform: Synchronize unmigrated apps: staticfiles, messages, cuser, starwars Apply all migrations: auth, sessions, contenttypes, tracking_fields, admin Synchronizing apps without migrations: Creating tables... Running deferred SQL... Installing custom SQL... Running migrations: Rendering model states... DONE Applying tracking_fields.0001_initial... OK Applying tracking_fields.0002_auto_20160203_1048... OK |
et nous voilà prêt à joueur les trackers.
Utilisation
On ne peut pas rêver plus simple, cela se résume à un décorateur sur le modele qui identifie quelles données sont modifiées, et un field histo
qui va lier le modele TrackingEvent de l’application TrackingFields, à ma table à surveiller. Et là, bien que le modele ait été modifié, inutile de faire un nouveau python manage.py migrate
, rien ne bougera, histo
sera une GenericRelation(). En effet, TrackingEvent repose sur ContenType aka “L’Infrastructure des Types de Contenu”. Si vous avez déjà tripoté la gestion des permissions, vous avez déjà dû vous y frotter;)
Pour la faire courte, en clair ça donne :
le models.py arrangé pour l’occasion du décorateur
from django.db import models from django.contrib.contenttypes.fields import GenericRelation from tracking_fields.decorators import track from tracking_fields.models import TrackingEvent @track('name', 'description') # decorator added class Movie(models.Model): """ Movie """ name = models.CharField(max_length=200, unique=True) description = models.CharField(max_length=200) # to get the changes made on movie histo = GenericRelation(TrackingEvent, content_type_field='object_content_type') def episodes(self): return Episode.objects.filter(movie=self) def __str__(self): return "%s" % self.name @track('name', 'scenario') # decorator added class Episode(models.Model): """ Episode - for Trilogy and So on ;) """ name = models.CharField(max_length=200) scenario = models.TextField() movie = models.ForeignKey(Movie) # to get the changes made on episode histo = GenericRelation(TrackingEvent, content_type_field='object_content_type') def __str__(self): return "%s" % self.name |
bon là c’est simplissime comme une recette de pate à crêpes: 3 imports de rigueur, le décorateur et la GenericRelation() on mélange le tout et ca donne ce qui suit J’ai, au passage, rajouté une fonction episodes à ma classe Movie, dont je vous reparlerai plus bas.
le template de la DetailView (pour afficher uniquement les details d’un film) qui va bien
<table> <caption>History of the modification of {{ object }} </caption> <thead> <tr><th>Old Value</th><th>New Value</th><th>By</th><th>at</th></tr> </thead> <tbody> {% for h in object.histo.all %} {% for f in h.fields.all %} <tr><td>{{ f.old_value }}</td><td>{{ f.new_value }}</td><td>{{ h.user }}</td><td>{{ h.date }}</td></tr> {% endfor %} {% endfor %} </tbody> </table> |
A présent si je me rends dans ma page pour modifier le scénario d’un Episode, mon template ci dessus, ne m’affichera pas ces modications ! Pourquoi bou diou ? Parce qu’ici j’affiche “l’histo” de Movie pas de Episode… On comprend à présent ici mon intéret pour le sous formulaire. Le “problème” aurait été masqué si je m’étais arrêté à un seul simple formulaire.
Corrigeons
c’est là qu’entre en jeu la fonction episodes
à ma classe Movie
, pour me permettre d’itérer dessus et afficher tout le toutim
le template de la DetailView qui va bien (bis)
<table> <caption>History of the modifications of {{ object }} </caption> <thead> <tr><th>Old Value</th><th>New Value</th><th>By</th><th>at</th></tr> </thead> <tbody> {% for h in object.histo.all %} {% for f in h.fields.all %} <tr><td>{{ f.old_value }}</td><td>{{ f.new_value }}</td><td>{{ h.user }}</td><td>{{ h.date }}</td></tr> {% endfor %} {% endfor %} </tbody> </table> {% for ep in object.episodes %} {% if ep.histo.all %} <table> <caption>history of the modifications of Episode</caption> <thead> <tr><th>Old Value</th><th>New Value</th><th>By</th><th>at</th></tr> </thead> <tbody> {% for h in ep.histo.all %} {% for f in h.fields.all %} {% if f.old_value == f.new_value %} {# they are the same when the new value is created to avoid to display "null" #} {% else %} <tr><td>{{ f.old_value }}</td><td>{{ f.new_value }}</td><td>{{ h.user }}</td><td>{{ h.date }}</td></tr> {% endif %} {% endfor %} {% endfor %} </tbody> </table> {% endif %} {% endfor %} |
Voili voilou ! Et en prime, si vous êtes curieux, coté admin, vous avez aussi la liste de toutes les modifications si besoin ;)
Aux utilisateurs avertis qui diraient :
pourquoi l’avoir recodé coté front puisque c’est déjà géré coté admin sans lever le petit doigt ?
Parce que George Lucas veut montrer les modifications apportées à son bébé StarWars par Walt Disney, au monde entier pardis !
Ah un détail en passant : dans l’admin la vue qui affiche la liste des modifications donne : “Episode Object” ou “Movie Object”. Pour eviter ça, zavez dû remarquer que j’ai mis la fonction __str__
dans mes modèles ce qui vous rendra une valeur plus “lisible” sur ce qui a été modifié.
Conclusion :
Dans la vraie vie de votre serviteur, ne se voyait pas créer un modele “history” lié “physiquement” par une FK à chaque modèle, entreprenait de chercher au travers de la toile quelques ressources.
C’est finallement sur #django-fr@freenode qu’il a posé la question et a obtenu de Gagaro le grââl : une application nommée tracking-fields, dont il est l’auteur.
Pour une fois qu’il fait sa faignasse en ne codant pas tout by himself, ça fait plaisir de tomber sur une appli pareille !
Si vous voulez jouer avec le code de ce gestionnaire de films c’est par ici la bonne soupe
On a reproché à la communauté de Twisted que c’était un silo fermé. Une lib écrite pour Twisted ne marchait que pour Twisted.
Puis on a reproché à la communauté de gevent la même chose.
Et maintenant la communauté d’asyncio recommence à faire la même erreur.
Regardez, pleins de libs compatibles asyncio, c’est génial non ?
Je ne vais pas dire non. Ça boost l’utilisabilité, l’adoption, etc.
Mais c’est aussi un énorme travail qui passe à côté de toute l’expérience (bugs, cas extrêmes, best practices, perfs…) des communautés précédentes. Et qui a une date de péremption, qui sera foutu à la poubelle à la prochaine vague.
Pourquoi ?
Parce que toutes ces libs se concentrent sur l’implémentation.
Vous ne pouvez pas réutiliser le code d’une lib STMP Twisted, car elle est liée au reactor. Pourtant cette lib contient des milliers d’infos utiles, la gestion de ce serveur bizarre, la correction de cette erreur de calcul de date, qui n’ont rien à voir avec Twisted.
C’est la même chose avec ces libs pour asyncio.
Que faire alors ?
Et bien d’abord écrire une lib neutre. Qui contient des choses comme :
Il faut écrire cette lib de manière à ce qu’elle puisse être réutilisée dans tout contexte. À base de callbacks simples, de hooks, de points d’entrées.
Puis, vous rajoutez dans un sous-module, le support pour votre plateforme favorite. Un adaptateur qui utilise ces hooks pour Twisted, ou asyncio, ou gevent.
Cela a de multiples bénéfices:
Toutes les plateformes ont une manière ou un autre pour attaquer ce problème. Twisted par exemple a une classe protocole qui est indépendante du reactor. Oui, mais elle est dépendante de la manière de penser en Twisted. Personne ne documente ces protocoles de manière neutre. Personne n’utilise ces protocoles de manière neutre.
gevent utilise carrément le monkey patching pour essayer de se rendre transparent. Évidemment ça veut dire que c’est très dépendant de l’implémentation. Si CPython change, ça casse. Si on utilise une implémentation différente de Python, ça ne marche pas. Si on fait des expérimentations comme actuellement sur le JIT, les résultats sont imprévisibles.
async
/await
a l’énorme bénéfice de proposer une interface commune à tout travail asynchrone. Fut-ce de l’IO, du thread, du multi-processing, du sous-processing, du multi-interpretteur ou des callbacks ordinaires… Cela va donc énormément gommer ces problèmes de compatibilité, même si la séparation des responsabilités que je recommande n’est pas suivie. Mais pour le moment tout le monde n’implémente pas __await__
. Et si __await__
lance le code sur l’autre plateforme, ça fait un truc en plus à gérer. Ce n’est pas tout à faire neutre.
Attention, je comprends très bien que cette séparation que je recommande ne soit pas suivie.
C’est très difficile de faire une API agnostique par rapport à la plateforme. Ça demande beaucoup plus de taf, de connaissance, etc. Je suis le premier à ne pas le faire pas fainéantise ou ignorance.
Mais il faut bien comprendre qu’à chaque fois, on réinvente la roue, une roue jetable par ailleurs.
Bien entendu, je dis ça pour l’async, mais c’est vrai pour tout.
Par exemple, des centaines de code ont leur propre moyen de définir un schéma et valider les données en entrée. Les ORM sont particulièrement coupable de cela, les libs de form aussi, mais on a tous codé ce genre de truc. C’est idiot, c’est un code qui n’a pas à être lié à une plateforme.
Des centaines de libs ont leur code de persistance lié à une plateforme. Même celles qui utilisent un ORM, au final, se lient à certaines bases de données (raison pour laquelle je suis GraphQL de très près).
La généricité a ses limites, et c’est toujours un compromis entre le cout immédiat, et le bénéfice futur. Si on fait tout générique, on se retrouve avec un truc qui évolue à 2 à l’heure et qui a 15 surcouches pour faire un print. On se retrouve avec Zope. Dont personne, et c’est ironique, ne réutilise les composants parce que c’est devenu trop compliqué de les comprendre.
Car évidemment, qui dit découplage, dit doc bien faire, qui explique clairement comment bénéficier de ce découplage. Mais dit aussi que le code utilisant les adapteurs doit être aussi simple que si on avait un fort couplage, ce qui est dur à faire.
Et on tombe ici sur un autre problème : la compétence pour faire ce genre de code. Si il faut 10 ans d’expérience pour faire une libre propre, alors on va réduire considérablement le nombre de personnes qui vont oser code des libs.
Aussi cet article n’est en aucun cas un savon que je souhaite passer aux auteurs. Merci de coder ces libs. Merci de donner de votre temps.
Non, cet article est juste là pour dire : dans la mesure du possible, il est très bénéfique sur le long terme de se découpler de la plateforme.
Comme je vous l’ai dit dernièrement, le packaging et les performances sont deux points qui méritent d’être améliorés en Python.
Nuikta est un projet qui vise à tacler ces deux problèmes d’un coup en compilant le code Python pour obtenir un exécutable autonome sous forme de code machine.
Plus besoin de s’inquiéter d’avoir la bonne version de Python installée. Plus besoin de se soucier d’avoir Python, ou une lib quelconque installée. Et en prime, le compilateur implémente le plus d’optimisations qu’il peut.
Nuitka est un projet en cours et son équipe est petite. Les progères sont sérieux, mais lents. Histoire d’encourager tout ça, je fais un don de 50€ à l’auteur.
Pour donner à votre tour, c’est par ici.
Je suis méga à la bourre. Entre les tickets github qui s’accumulent, les comments auxquels j’ai toujours pas répondu et la liste d’articles à écrire qui augmente au lieu de diminuer (mais comment, bordel, comment ?), j’ai vraiment du mal à suivre.
Je dois toujours un article sur Polymer + Crossbar à Tavendo. Et il faut que je fasse un tuto sur l’authentification également (en attendant, y a des exemples plus à jour).
Fichtre.
En attendant, je vais en profiter pour faire un article vite fait sur la dernière release, puisque Crossbar, le routeur WAMP, passe en 0.12.
Comme d’hab, correction de bugs, amélioration du support de Python 3, plus de docs et d’exemples, blablabla…
Mais ce qui est vraiment intéressant, c’est l’historique des évènements.
Normalement un évènement est éphémère, dans le sens où une fois qu’il a été propagé, vous ne pouvez plus le recevoir. Si vous arrivez après la fête, c’est terminé.
C’est un problème, par exemple si vous redémarrez un client qui a besoin de ces évènements. Ou si vous implémentez un client qui veut savoir ce qui vient de se passer avant de se pointer, comme dans le cas d’un chat : on veut avoir les derniers messages postés.
Par défaut, l’historique n’est pas activé, puisqu’il y un cout pour chaque pub/sub. On doit explicitement le demander pour chaque event dans le fichier de config :
{ "name": "realm1", "roles": [ ], "store": { "type": "memory", # ou stocker l'historique "event-history": [ { "uri": "mon.uri.pour.un.event", # quel type event "limit": 10000 # combien d’events stocker } ] } } |
type
n’accepte pour le moment que memory
, qui est une simple liste en mémoire dans crossbar, et bouffe donc de la RAM. On perd aussi l’historique au redémarrage du routeur, mais ça à l’avantage d’être très rapide.
Pour la prochaine version, Tavendo va implémenter un stockage dans une base lmdb
et si ils font une belle API, on peut s’attendre à voir fleurir des backends pour SQLAlchemy, Django, Redis, etc.
event-history
prend un liste d’events (les URIs peuvent utiliser les jokers introduits dans la version précédente), on met la limite du nombre total d’events à stocker pour cet URI en particulier.
Pour profiter de l’historique côté client, il faut obligatoirement avoir un abonnement à un event dont les messages sont sauvegardés. On ne peut pas récupérer l’historique de messages auxquels ont est pas abonnés : forcer l’abonnement oblige en effet le client à passer le check des permissions.
Par exemple, en JS, d’abord on s’inscrit:
var promise = session.subscribe('mon.uri.pour.un.event', function (args, kwargs, details) { // bon là vous faites bien ce que vous voulez avec les nouveaux events //, car ça n’a rien à voir avec l’historique } ) |
Puis on demande les events:
promise = promise.then(function (sub) { // L’abonnement retourne un objet "subcription" qui possède l’id // dont on a besoin pour demander l’historique des events. // On fait un petit RPC sur la meta API 'wamp.subscription.get_events' // qui demande aux routeurs tous les X derniers events qui matchent // notre abo. Ici x = 10 return session.call('wamp.subscription.get_events', [sub.id, 10]); ) |
Et enfin, on a droit à l’histo:
promise.then(function (history) { console.log(history.length, " events:"); history.forEach(function(event){ console.log(event.timestamp, event.publication, event.args[0]); }) }); |
En Python, le code pour récupérer l’histo est logiquement:
import asyncio from os import environ from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): async def onJoin(self, details): def on_event(i): print("Got: {}".format(i)) # pareil on shoppe la subscription sub = await self.subscribe(on_event, u'mon.uri.pour.un.event') # et on demande la liste des 10 derniers events pour cet abo events = await self.call('wamp.subscription.get_events', sub.id, 10) # et on boucle. Et on kiff await parceque putain c’est pratique. for event in events: print(event['timestamp'], event['publication'], event['args'][0]) def onDisconnect(self): asyncio.get_event_loop().stop() if __name__ == '__main__': runner = ApplicationRunner("ws://127.0.0.1:8080/ws", 'realm1') runner.run(Component) |
L’autre point phare de cette release, c’est la dépréciation de Mozilla Persona comme méthode d’authentification (le projet est dead) et la promotion de deux nouvelles méthodes: les certificats TLS et les paires de clés publiques/privées (Curve25519 pour le moment).
C’est une très bonne nouvelle, car ça veut dire plus de mots de passe dans les fichiers de configuration en production pour identifier vos propres clients qui ont forcément des privilèges supplémentaires.
Je reviendrais là dessus en faisant le tuto sur l’authentification.
Une release chouette donc. Mais qui introduit plein de dépendances à des extensions en C qui pourraient être optionnelles, ce qui rend l’installation plus compliquée. Je suis en train de discuter avec la team pour voir si on peut arranger ça, mais Tobias à l’air plutôt pour les garder. Si vous aussi vous voulez garder la simplicité de la base pure Python, rejoignez la discussion.
Enfin, on a pu voir l’annonce d’une feature très intéressante : le chiffrement end-to-end des messages WAMP. Ça, c’est chouette. C’est pas encore implémenté, mais ça veut dire que la prochaine release, vous pourrez probablement envoyer des messages à travers le serveur WAMP sans que celui-ci puisse les lire.
La communauté Python est assez d’accord ces derniers temps. Maintenant que le plus gros de la débâcle Python2/3 est derrière nous (en attendant le contre coup des retardataires de 2020) et qu’on a un modèle d’IO async bien clean, les trucs qui fâchent sont assez clairement délimités:
Sur ces questions, du travail est activement en cours.
Pour le multi CPU, un modèle propre permettra d’utiliser plusieurs interpréteurs en parallèle en partageant des valeurs
sans avoir à les sérialiser.
Pour les perfs, ce sera un taf plus long, mais:
Bref, y a du potentiel.
Pour le packaging, les wheels vont enfin arriver sous Linux, ce qui fait qu’on pourra bientôt pip installer des binaires partout. nuikta, le compilateur Python, supporte maintenant await/async.
On est sur la bonne route.
L’année 2016 va être trop cool, et dans mon enthousiasme, j’aimerais écrire à propos de choses que j’aimerais vraiment voir arriver dans Python.
Beaucoup de codes en Python ressemblent à ça :
try: val = faire un truc except MonErrorALaNoix: val = "valeur par default" |
Par exemple :
try: val = stuff[index] except (IndexError, TypeError): val = None |
Ce sont des opérations si courantes qu’on a plein de raccourcis comme dict.get
ou next(i, None)
. En effet, en Python try
/except
n’est pas juste un mécanisme de gestion d’erreur, c’est un mécanisme de contrôle de flux à part entière.
Car franchement, ça fait chier de se taper 4 lignes pour écrire ça. En effet, on a bien les expressions ternaires pour les if
/else
:
val = truc if bidule else machine |
Et bien il exist un PEP (rejeté) qui propose ça:
val = faire un truc except MonErrorALaNoix: "valeur par default" |
J’adore. C’est pratique, générique, propre.
Bien entendu ça peut être abusé, comme les expressions ternaires, pour faire de la merde illisible. Mais j’ai rarement vu le cas pour les précédentes, donc ça devrait aller.
Les générateurs, c’est formidable. C’est iterable. On peut les utiliser partout où on utilise les listes.
Sauf si on doit les limiter en taille.
Alors là, c’est relou.
Par exemple, récupérer les carrés des nombres pairs entre 0 et 100, puis limiter le tout a 10 éléments après les 5e:
from itertools import islice g = (x * x for x in range(100) if x % 2 == 0) g = islice(g, 5, 15) |
Ca serait tellement plus simple de pouvoir faire:
g = (x * x for x in range(100) if x % 2 == 0)[5:10] |
Si vous voulez tous les carrés des nombres au-dessus de 5, vous pouvez faire:
(x * x for x in numbres if x > 5) |
Mais si vous voulez tous les nombres à partir du moment où vous rencontrez un nombre au-dessus de 5 ?
from itertools import dropwhile numbers = dropwhile(lambda x: x > 5, numbers) (x * x for x in numbres) |
Alors certes, je ne suis pas pour transformer Python en Haskel et balancer des formules magiques comme:
(x * x for x in numbers[a -> a > 5]) |
Mais juste m’éviter l’import et pouvoir faire ça:
def start(x): return x > 5 (x * x for x in numbers[start:]) |
Ca serait cool.
Je suis hésitant sur cette feature car c’est très tentant de faire de one liners trop longs avec, mais:
(x.split()[1] for x in truc if x.split()[1] == 1) |
C’est con de faire 2 fois split
quand même.
(y for x in truc with x.split()[1] as y if y == 1) |
Bon, ça peut rapidement devenir illisible, donc à méditer.
Je cherche toujours à comprendre pourquoi async
est nécessaire.
Avec les générateurs, la présence de yield
fait détecter automatiquement la fonction comme fonction génératrice.
Avec await
, ça devrait être pareil:
def stuff(): await bidule # bim, c’est une coroutine ! |
Pas besoin de async
. Si on veut faire une coroutine sans un seul await
, il y a toujours @asyncio.coroutine
.
async
reste très utile pour async for
et async with
, mais perso j’aurais préféré avoir un await with et un await for et pas de async
.
Après, le prenez pas mal hein. J’adore async
/await
. Vive asyncio
! Mais la syntaxe pourrait être plus naturelle, plus proche du comportement des générateurs, d’autant que ça se base dessus.