PROJET AUTOBLOG


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

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

⇐ retour index

Augmenter son volume de sperme pour des super éjaculations faciales. 18

mardi 24 janvier 2017 à 00:36

Un sale titre putaclic comme je les aime :)

Comme tous les 2 mois je parts en mission spéciale pour m’adonner aux pratiques les plus salaces afin d’assouvir mes pulsions DSKniennes.

Et comme 99.99% des mecs j’adore les éjacs faciales mais aussi sur les pieds (fétichisme oblige) ou les fesses ou encore une bonne paire de nichons bien juteux.
Il y a quelques années je m’étais renseigné sur des pilules pour augmenter le volume de sperme mais c’était du pipeau, j’ai donc regardé la composition du sperme et grosso modo la voici:

Le sperme contient de nombreux éléments nourriciers pour le spermatozoïde :
vitamines C et B12, sels minéraux comme le calcium, le magnésium, le phosphore, le potassium et le zinc, des sucres (fructose et sorbitol).

Je me suis penché sur les éléments les plus courants à trouver à savoir la vitamine C, B12, magnésium, zinc et fructose.

vitamine C = orange
B12 = banane
magnesium et zinc = cacao
fructose = les fruits

Je me suis donc mis en tête d’ingurgiter quotidiennement ces ingrédients afin de tester une éventuelle augmentation du volume spermique.
Mon test n’est basé sur absolument aucun étude scientifique, mais de toutes façons c’est que des produits naturels, et en plus ça file la pêche.

Bien évidement vous n’allez pas jouer au pompier du jour au lendemain mais j’ai pu noter une certaine augmentation du volume sans que ce soit non plus les chutes du niagara (le titre c’est juste pour être en premier dans les forums d’ados) mais je les trouves supérieures tout de même avec un certain avantage, une femme m’a dit que mon liquide séminal avait le goût de fruits, elle avait l’air d’en être plutôt contente.

 

Passons au cocktail !

2 fois par jour, en général 10h du math et 16/17h:

Dans un mixer versez:

Mixez le tout pendant 5 minutes et servez frais !

Faut faire ce “régime” pendant au moins quelques semaines en buvant pas mal d’eau mais ça c’est la base, au moins 1 à 2 litres d’eau par jour pour que le corps élimine bien sinon il s’acidifie. Vous devriez voir les premiers résultats, madame sera ravie.
Du 100% Bio ! Bon pour le teint.

ban

chériiiiiiie, tes vitamines arrivent…

Aller cadeau, la HD ça rend tout de suite mieux.  

PS: j’ai fait plusieurs tests et c’est celui qui me donne le plus de satisfaction, si vous avez des variantes ou des suggestions allez-y. A côté de ça j’ai une alimentation qui exclue tout produit surgelé ou transformé par l’industrie agro alimentaire, paraît qu’on appelle ça le régime paléo, perso je fais pas de régime, je bouffe comme ma grand-mère et mes parents, pas de junk food et je me sens bien.
Je me gène pas me défoncer un plateau de fruits de mer à l’occas (au passage les huîtres sont blindées de zinc, B12, omega3 et ça a une texture de chatte alors pourquoi s’en priver ?!)

 

Oui je sais c’est très réducteur pour la dignité des femmes, ça rappelle les heures les plus sombres de notre histoire, la météorite qui a fait disparaître les dinosaures et ça prédit même l’arrivé de Nibiru. Pour les féministes défoulez-vous, mais n’oubliez pas vos cachets et le rendez-vous de demain chez le psy.

 

A poil les putes !

Je peux faire un clone de Twitter en un week end 16

vendredi 20 janvier 2017 à 16:01

Il ne se passe pas un mois sans que je ne lise le commentaire d’un abruti qui annonce être capable de réécrire service X en quelques jours. Des projets et des projets de clones pour le prouver.

Twitter, Uber, Imgur, whatever.

Une variante est de juger la stack d’un service en prétendant qu’on pourrait faire beaucoup mieux avec moins.

Je ne sais pas si c’est de la bêtise, de l’ignorance ou de l’arrogance. Probablement des trois.

Prenons par exemple Twitter. Easy non ? Des messages de 140 caractères, quelques tags.

Mais, mais, mais les amis. Ca c’était Twitter le premier week-end de sa sortie aussi.

Maintenant Twitter c’est beaucoup plus que ça:

Bien entendu avec son succès Twitter doit maintenant scaler et assurer:

Aujourd’hui ce service, c’est des millions de lignes de code.

En prime, un service à succès n’est pas que du code, c’est aussi une énorme infra. Une logistique de dingue. Et un effort marketing colossal.

Personne ne peut reproduire ce que fait X actuellement en un an de travail. Je ne parle pas d’un week-end.

Les projets que l’on voit sont des “représentation du concept clé de X”.

Ca ne veut pas dire qu’il ne faut pas essayer de copier, concurrencer, proposer des alternatives, etc. Faut juste pas prendre les gens pour des cons.

Bref, à tous les prétendants au “c’est super simple” présents, passés et futurs :

Le formatage des strings en long et en large 17

lundi 16 janvier 2017 à 15:53

Un bon article bien long. Je sens que ça vous avait manqué :) Musique ?

Un problème qui se retrouve souvent, c’est le besoin d’afficher un message qui contient des valeurs de variables. Or, si en Python on privilégie généralement “il y a une seule manière de faire quelque chose”, cela ne s’applique malheureusement pas au formatage de chaînes qui a accumulé bien des outils au fil des années.

TL;DR

Si c’est juste pour afficher 2, 3 bricoles dans le terminal, utilisez print() directement:

>>> print("J'ai", 3, "ans")
J'ai 3 ans
>>> print(3, 2, 1, sep='-')
3-2-1

Si vous avez besoin d’un formatage plus complexe ou que le texte n’est pas que pour afficher dans le terminal…

Python 3.6+, utilisez les f-strings:

>>> produit = "nipple clamps"
>>> prix = 13
>>> print(f"Les {produit} coûtent {prix:.2f} euros")
Les nipple clamps coûtent 13.00 euros

Sinon utilisez format():

>>> produit = "nipple clamps"
>>> prix = 13
>>> print("Les {} coûtent {:.2f} euros".format(produit, prix))
Les nipple clamps coûtent 13.00 euros

Si vous êtes dans le shell, que vous voulez aller vite, ou que vous manipulez des bytes, vous pouvez utiliser “%”, mais si ça ne vous arrive jamais, personne ne vous en voudra:

>>> produit = "nipple clamps"
>>> prix = 13
>>> print("Les %s coûtent %.2f euros" % (produit, prix))
Les nipple clamps coûtent 13.00 euros

N’utilisez jamais string.Template.

Si vous avez un gros morceau de texte ou besoin de logique avancée, utilisez un moteur de template comme jinja2 ou mako. Pour l’i18n et la l10n, choisissez une lib comme babel.

Avec print()

Par exemple, si j’ai :

produit = "nipple clamps"
prix = 13

Et je veux afficher :

"Les nipple clamps coûtent 13 euros"

La manière la plus simple de faire cela est d’utiliser print():

>>> print("Les", produit, "coûtent", prix, "euros")
Les nipple clamps coûtent 13 euros

Mais déjà un problème se pose : cette fonction insère des espaces entre chaque argument qu’elle affiche. Cela est ennuyeux si par exemple je veux utiliser le signe et le coller pour obtenir :

Les nipple clamps coûtent 13

print() possède un paramètre spécial pour cela : sep. Il contient le séparateur, c’est à dire le caractère qui va être utilisé pour séparer les différents arguments affichés. Par défaut, sep est égal à un espace.

Si je change ma phrase et que j’ai besoin d’espaces à certains endroits et pas à d’autres, il me faut définir un séparateur – ici une chaîne vide – et jouer un peu avec le texte :

>>> print("Les ", produit, " coûtent ", prix, "€", sep="")
Les nipple clamps coûtent 13

C’est mieux. Mais, ça commence à devenir moins lisible.

Maintenant que se passe-t-il si je veux utiliser une valeur numérique mais que j’ai besoin de la formater ?

Par exemple :

produit = "nipple clamps"
prix = 13
exo_taxe = 0.011

Et je veux tronquer le prix au centime de telle sorte que j’obtienne :

Les nipple clamps coûtent 13.01

Arf, ça va demander un peu plus de travail.

>>> total = round(prix + exo_taxe, 2)
>>> print("Les ", produit, " coûtent ", total, "€", sep="")

Bon, mais admettons que je veuille sauvegarder ce texte dans une variable ? Par exemple pour le passer à une fonction qui vérifie l’orthographe ou met la phrase en jaune fluo…

Dans ce cas ça devient burlesque, il faut intercepter stdout et récupérer le résultat :

>>> faux_terminal = io.StringIO()
>>> print("Les ", produit, " coûtent ", total, "€", sep="", file=faux_terminal)
>>> faux_terminal.seek(0)
>>> msg = faux_terminal.read()
>>> print(msg)
Les nipple clamps coûtent 13.01

Vous l’avez compris, print() est fantastique pour les cas simples, mais devient rapidement peu pratique pour les cas complexes : son rôle est d’être bon à afficher, pas à formater.

Avec +

A ce stade, un débutant va généralement taper “concaténation string python” sur son moteur de recherche et tomber sur l’opérateur +. Il essaye alors ça :

>>> "Les " + produit + " coûtent " + total + "€"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-19-126b156e23bd> in <module>()
----> 1 "Les " + produit + " coûtent " + total + "€"
 
TypeError: Can't convert 'float' object to str implicitly

Et il apprend par la même occasion que Python est fortement typé. On ne peut pas additionner des choux et des carottes disait ma prof de CE1, et donc on ne peut pas additionner "coûtent" (type str) et total (type float).

Il faut donc convertir total :

>>> "Les " + produit + " coûtent " + str(total) + "€"
'Les nipple clamps coûtent 13.01€'

C’est mieux que notre version avec print(), d’autant qu’on peut sauvegarder facilement tout ça dans une variable :

>>> total = round(prix + exo_taxe, 2)
>>> msg = "Les " + produit + " coûtent " + str(total) + "€"
>>> print(msg)
Les nipple clamps coûtent 13.01

Mais ça reste chiant à taper, et encore plus à modifier. Si je veux insérer quelque chose là dedans, il me faut faire très attention en déplaçant mes + et mes " sans compter calculer ma gestion des espaces.

La raison est simple : il est difficile de voir la phrase que j’essaye d’afficher sans bien étudier mon expression.

Par ailleurs, je suis toujours obligé de faire mon arrondi.

Pour cette raison, je recommande de ne pas utiliser + pour formater son texte, car il existe de bien meilleurs outils en Python.

Avec %

Là, on arrive à quelque chose de plus sympa !

L’opérateur % appliqué aux chaînes de caractères permet de définir un texte à trous, et ensuite de dire quoi mettre dans les trous. C’est une logique de template.

Elle est courte et pratique : c’est la méthode que j’utilise le plus actuellement dans un shell ou sur les chaînes courtes.

Par exemple, si je veux créer la chaîne:

Les nipple clamps coûtent 13€

Alors mon texte à trou va ressembler à :

Les [insérer ici le nom du produit] coûtent [insérer ici le prix du produit]€

Avec l’opérateur %, le texte à trous s’écrit :

Les %s coûtent %s€

%s marque les trous.

Pour remplir, on met les variables à droite, dans l’ordre des trous à remplir :

>>> total = round(prix + exo_taxe, 2)
>>> "Les %s coûtent %s€" % (produit, total)
'Les nipple clamps coûtent 13.01€'

Pas besoin de convertir total en str, et la phrase qu’on souhaite obtenir est facile à deviner en lisant l’expression.

%s veut dire “met moi ici la conversion en str de cet objet”. C’est comme si on appelait str(total).

Il existe d’autres marqueurs :

Ex:

>>> "%d" % 28.01
    '28'
>>> "%f" % 28
    '28.000000'
>>> "%x" % 28
    '1c'

La liste des marqueurs est disponible sur cette page de la doc.

En plus des marqueurs qui permettent de savoir où insérer la valeur et quel format lui donner, on peut aussi donner des précisions sur l’opération de formatage. On peut ainsi décider combien de chiffres après la virgule on souhaite, ou obliger la valeur à avoir une certaine taille :

>>> "%4d" % 28 # au moins 4 caractères
    '  28'
>>> "%04d" % 28 # au moins 4 chiffres
    '0028'
>>> "%.2f" % 28 # 2 chiffres après la virgule
    '28.00'

Ainsi notre exemple:

>>> total = round(prix + exo_taxe, 2)
>>> "Les %s coûtent %s€" % (produit, total)
    'Les nipple clamps coûtent 13.01€'

peut maintenant être réduit à :

>>> total = prix + exo_taxe
>>> "Les %s coûtent %.2f€" % (produit, total)
    'Les nipple clamps coûtent 13.01€'

Néanmoins un des défauts de % est qu’il n’accepte qu’un tuple, ou une valeur seule. Impossible de passer un itérable arbitraire :

>>> "Les %s coûtent %.2f€" % [produit, total]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-bb9b7acda392> in <module>()
----> 1 "Les %s coûtent %.2f€" % [produit, total]
 
TypeError: not enough arguments for format string

Et si vous voulez formater un tuple, il faut le mettre dans un tuple d’un seul élément, source de plantage :

>>> "Les données sont %s" % data
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-4e21e97b8a3f> in <module>()
----> 1 "Les données sont %s" % data
 
TypeError: not all arguments converted during string formatting
 
>>> "Les données sont %s" % (data, )
    "Les données sont ('nipple clamps', 13.01)"

Tout codeur Python s’est retrouvé un jour devant ce cas et s’est gratté la tête.

Par ailleurs, dès qu’il y a beaucoup de trous à combler dans le texte, ça devient vite difficile de savoir ce qui va où :

 "[%s] %s%s %s(%s) - %s%s%s"  % (
        datetime.datetime.now(),
        res,
        unit,
        type,
        variant,
        testers[0],
        testers[1],
        testers[2]
  )

Pour pallier ce problème, % peut accepter aussi un dictionnaire et avoir des trous nommés :

 "[%(date)s] %(value)s%(unit)s %(type)s(%(variant)s) - %(tester1)s%(tester2)s%(tester3)s"  % {
        "date": datetime.datetime.now(),
        "value": res,
        "unit": "m",
        "type": "3",
        "variant": "beta",
        "tester1": testers[0],
        "tester2": testers[1],
        "tester3": testers[2]
  }

On peut voir néanmoins que le pari n’est pas tout à fait gagné. Et on ne gagne pas tant que ça en lisibilité. Pour cette raison, les formatages complexes sont plus intéressants à faire avec format() que nous verrons plus loin.

Rappelez-vous néanmoins que depuis Python 3, format() ne fonctionne plus sur les bytes. % reste donc la seule option pour formater des paquets réseaux, des headers de jpeg et tout autre format binaire.

Formater les dates

Même si il est toujours recommandé d’utiliser une bonne lib pour manipuler les dates, Python permet déjà de faire pas mal de choses avec la lib standard.

En effet, certaines notions, comme le temps, ont une forme très différente entre celle utilisée pour les manipuler, et celles utilisées pour les représenter.

Pour cette raison, l’objet date de Python propose deux méthodes, strptime et strftime, pour gérer le format des dates.

La procédure pour gérer les dates se fait donc toujours en 3 parties, un peu comme l’encoding d’un texte :

  1. Créer une nouvelle date, soit à la main, soit à partir de données existantes.
  2. Manipuler les dates pour obtenir ce qu’on souhaite (un autre date, un durée, un intervalle, etc.
  3. Formater le résultat pour le présenter à l’utilisateur ou le sauvegarder à nouveau.

Pour récupérer une date existante, on va utiliser strptime (“str” pour string, “p” pour parse) :

>>> from datetime import datetime
>>> date = datetime.strptime("1/4/2017", "%d/%m/%Y")
>>> date.year
    2017
>>> date.day
    1

Le deuxième paramètre contient le motif à extraire de la chaîne de gauche : c’est l’inverse d’un texte à trous ! On dit “dans la chaîne de gauche, j’ai le jour là, le mois là et l’année là, maintenant extrais les”.

Pour formater une date, c’est la même chose, mais dans l’autre sens, avec strftime (“f” pour format) :

>>> date = datetime.now()
>>> date.strftime('%m-%d-%y')
    '04-01-17'

Le mini-langage pour formater les dates est documenté ici, et vous pouvez en apprendre plus sur les dates sur une petite intro dédiée.

Avec format()

% a ses limites. C’est un opérateur pratique pour les petites chaînes et les cas de tous les jours, mais si on a beaucoup de valeurs à formater, cela peut devenir vite un problème. Il possède aussi quelques cas d’utilisation qui causent des erreurs inattendues puisqu’il n’accepte que les tuples.

format() a été créé pour remédier à cela. Dans sa forme la plus simple, il s’utilise presque comme %, mais les marqueurs sont des {} et non des %s :

>>> "Les {} coûtent {}€".format(produit, prix)
    'Les nipple clamps coûtent 13€'

Mais déjà, format() se distingue du lot car il permet de choisir l’ordre d’insertion :

>>> "Les {1} coûtent {0}€".format(prix, produit)
    'Les nipple clamps coûtent 13€'

La méthode accepte également n’importe quel itérable grâce à l’unpacking :

>>> "Les {} coûtent {}€".format(*[produit, prix])
    'Les nipple clamps coûtent 13€'

Formater un tuple seul est aussi très simple :

>>> "Les données sont {}".format(data)
    "Les données sont ('nipple clamps', 13.01)"

Mais là où format() est bien plus pratique, c’est quand on a beaucoup de données et qu’on veut nommer ses trous :

 "[{date}] {value}{unit} {type}({variant}) - {testers[0]}{testers[1]}{testers[2]}".format(
        date=datetime.datetime.now(),
        value=res,
        unit="m",
        type="3",
        variant="beta",
        testers=testers
  )

Le texte à trous est plus clair, et on peut utiliser l’index d’une liste directement dedans.

Un autre avantage non négligeable, est que format() n’utilise pas de nombreux marqueurs différents comme %f, %d, %s

A la place, il n’y a que {}, et format() appelle en fait pour chaque valeur sa méthode … __format__, ou en l’absence de celle-ci appelle str().

>>> a = 42
>>> a.__format__("")
    '42'

Chaque objet peut définir __format__, et accepter ses propres options:

>>> a.__format__(".2f")
    '42.00'
>>> from datetime import datetime
>>> datetime.now().__format__('%d %h')  # pas besoin de strftime !
    '20 Sep'

Et format() utilise tout ce qui est après : dans un trou pour le passer à __format__:

>>> "{foo:.2f} {bar:%d %h}".format(foo=42, bar=datetime.now())

Cela permet des formatages très poussés.

Les f-strings

Les f-strings sont une nouvelle fonctionnalité de Python 3.6, et elles sont merveilleuses, combinant les avantages de .format() et %, sans les inconvénients :

>>> produit = "nipple clamps"
>>> prix = 13
>>> print(f"Les {produit} coûtent {prix:.2f} euros")
Les nipple clamps coûtent 13.00 euros

En gros, c’est la syntaxe de format(), mais sans sa verbosité.

En prime, on peut utiliser des expressions arbitraires dedans:

>>> print(f"Les {produit.upper()} coûtent {prix:.2f} euros")
Les NIPPLE CLAMPS coûtent 13.00 euros

A première vue, ça ressemble à du exec, et donc à un parsing lent, doublé d’une une grosse faille de sécurité.

Et bien non !

C’est en fait du sucre syntaxique, et au parsing du code, Python va transformer l’expression en un truc du genre:

"Les " + "{}".format(produit.upper()) + " coûtent " + "{:.2f}".format(prix) + " euros"

Mais en bytecode. Pas d’injection de code Python possible, et en prime, les f-strings sont aujourd’hui la méthode de formatage la plus performante.

En clair, si vous êtes en 3.6+, vous pouvez oublier toutes les autres.

Méthodes de l’objet str

Parfois, on ne veut pas remplir un texte à trous. Parfois on a déjà le texte et on veut le transformer. Pour cela, l’objet str possède de nombreuses méthodes qui permettent de créer une nouvelle chaîne, qui possède des traits différents :

>>> "    strip() retire les caractères en bouts de chaîne   ".strip() # espace par défaut
    'strip() retire les caractères en bouts de chaîne'
>>> "##strip() retire les caractères en bouts de chaîne##".strip("#")
    'strip() retire les caractères en bouts de chaîne'
>>> "##strip() retire les caractères en bouts de chaîne##".lstrip("#")
    'strip() retire les caractères en bouts de chaîne##'
>>> "##strip() retire les caractères en bouts de chaîne##".rstrip("#")
    '##strip() retire les caractères en bouts de chaîne'
>>> "wololo".replace('o', 'i') # remplacer des lettres
    'wilili'
>>> "WOLOLO".lower() # changer la casse
    'wololo'
>>> "wololo".upper()
    'WOLOLO'
>>> "wololo".title()
    'Wololo'

Notez bien que ces méthodes créent de nouvelles chaînes. L’objet initial n’est pas modifié, puisque les strings sont immutables en Python.

Parmi les plus intéressantes, il y a split() et join(), qui ont une caractéristique particulière : elles ne transforment pas une chaîne en une autre.

split() prend une chaîne, et retourne… une liste !

>>> "split() découpe une chaîne en petits bouts".split() # défaut sur espaces
    ['split()', 'découpe', 'une', 'chaîne', 'en', 'petits', 'bouts']
>>> "split() découpe une chaîne en petits bouts".split("e")
    ['split() découp', ' un', ' chaîn', ' ', 'n p', 'tits bouts']

join() fait l’inverse, et prend un itérable (comme une liste), pour retourner… une chaîne :)

>>> "-".join(['join()', 'recolle', 'une', 'chaîne', 'DEPUIS', 'des', 'petits', 'bouts'])
    'join()-recolle-une-chaîne-DEPUIS-des-petits-bouts'

Avec juste ces méthodes, on peut s’autoriser pas mal de fantaisies avec le texte, et le nombre de méthodes disponibles est assez large :

>>> dir(str)
    [...
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

Donc référez-vous à la doc.

Caractères spéciaux

On vous a menti !

Quand on écrit "" en Python on ne crée pas une chaîne. En fait, on écrit une instruction qui dit à Python comment créer une chaîne.

La différence est subtile, mais importante. "" n’est PAS la chaîne, "" est une instruction, une indication pour Python de comment il doit procéder pour créer une chaîne.

Et on peut donner des instructions plus précises à Python. Par exemple on peut dire, “insère moi ici un saut de ligne”. Cela se fait avec le marqueur "\n".

>>> print('un saut\n de ligne')
un saut
 de ligne

\n n’est PAS un saut de ligne. C’est juste une indication donnée à Python pour lui dire qu’ici, il doit insérer un saut de ligne quand il créera la chaîne en mémoire.

Il existe plusieurs marqueurs de ce genre, les plus importants étant \n (saut de ligne) et \t (tabulation).

Pour rentrer les caratères \t\n, il faut donc dire à Python explicitement qu’on ne veut pas qu’il insère un saut de ligne ou une tabulation, mais plutôt ces caractères.

Cela peut se faire, soit avec le caractère d’échappement \ :

>>> print('pas un saut\\n de ligne')
pas un saut\n de ligne

Soit en désactivant cette fonctionalité avec le préfixe r, pour raw string:

>>> print(r'pas un saut\n de ligne')
pas un saut\n de ligne

Cette fonctionalité est très utilisée pour les noms de fichiers Windows et les expressions rationnelles car ils contiennent souvent \t\n.

Bytes, strings et encoding

Python fait une distinction très forte entre les octets (type bytes) et le texte (type str). La raison est qu’il n’existe pas de texte brut dans la vraie vie, et que tout ce que vous lisez : fichiers, base de données, socket réseau, et même votre code source (!) est un flux d’octets encodés dans un certain ordre pour représenter du texte.

En Python, on a donc le type str pour représenter du texte, une forme d’abstraction de toute forme d’encodage qui permet de manipuler ses données textuelles sans se soucier de comment il est représenté en mémoire.

En revanche, quand on importe du texte (lire, télécharger, parser, etc) ou qu’on exporte du texte (écrire, afficher, uploader, etc), il faut explicitement convertir son texte vers le type bytes, qui lui a un encoding en particulier.

Ce principe mérite un article à lui tout seul, et je vous renvoie donc à la page dédiée du blog.

Templating

Parfois on a beaucoup de texte à gérer. Par exemple, si vous faites un site Web, vous aurez beaucoup de HTML. Dans ce cas, faire tout le formatage dans son fichier Python n’est pas du tout pragmatique.

Pour cet usage particulier, on utilise ce qu’on appelle un moteur de template, c’est à dire une bibliothèque qui va vous permettre de mettre votre texte à trous dans un fichier à part. Les moteurs de templates sophistiqués vous permettent de faire quelques opérations logiques comme des boucles ou des conditions dans votre texte.

La première chose à savoir, c’est de ne PAS utiliser string.Template. Cette classe ne permet d’utiliser aucune logique, et n’a aucun avantage par rapport à .format().

Pour le templating, il vaut mieux se pencher vers une lib tièrce partie. Les deux principaux concurrents sont Jinja2, le moteur de templating le plus populaire en Python, créé par l’auteur de Flask. Et le moteur de Django, fourni par défaut par le framework.

Depuis Django 1.10, le framework supporte aussi jinja2, donc je vais vous donner un exemple avec ce dernier. Sachez qu’il existe bien d’autres moteurs (mako, cheetah, templite, TAL…) mais jinja2 a plus ou moins gagné la guerre.

Un coup de pip :

pip install jinja2

On fait son template dans un fichier à part, par exemple wololo.txt:

Regardez je sais compter :
  {% for number in numbers %}
    - {{number}}
  {% endfor %}

Puis en Python:

import jinja2
 
# On définit où trouver les fichiers de template. Ex. le dossier courant:
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader('.'))
 
# On dit à jinja de charger le template à partir de son chemin relatif
template = jinja_env.get_template('wololo.txt')
 
# On crée un contexte, c'est à dire une collection d'objects qu'on veut rendre
# accessibles dans le template. Généralement, c'est un dictionnaire dont les
# clés sont les noms des variables telles qu'elle apparaîtront dans le template
# et les valeurs ce que contiendront ces variables.
ctx = {"numbers": [1, 2, 3]}
 
# On demande le "rendu" du template, c'est à dire le mélange du template
# et du contexte.
resultat = template.render(ctx)
 
print(resultat)
 
# Ce qui donne :
# - 1
# - 2
# - 3

i18n et l10n

L’i18n, pour ‘internationalisation’ (soit 18 lettres entre le i et le n) est le fait d’organiser votre code de telle sorte que son interface puisse s’adapter à plusieurs cultures. La l10n, pour ‘localisation’ (soit 10 lettres entre le l et le n), est le fait de fournir avec son code les données nécessaires pour une culture en particulier.

Par exemple, marquer toutes vos chaînes de caractères comme étant traductibles et fournir un mécanisme de substitution de la chaîne est de l’i18n. Fournir un fichier de traduction pour l’espagnol pour ces chaînes est de la l10n.

La combinaison des deux est parfois nommée g11n pour “globalization”.

La g11n peut inclure:

Plus qu’un article, c’est un dossier qu’il faudrait faire sur ces sujets car c’est très, très vaste.

La traduction de texte peut être faite directement avec le module gettext fourni en Python. Certains formatages de nombres et de dates sont aussi faisables avec la stdlib grâce au module locale.

Néanmoins dès que vous voulez faire quelque chose de plus gros avec la g11n, je vous invite à vous tourner vers des libs externes.

Babel est la référence en Python pour le formatage du texte et des nombres, et il existe des extensions pour les moteurs de template les plus populaires. La lib inclut une base de données aussi à jour que possible sur les devises, les noms de pays, les langues…

pendulum est idéal pour la manipulation des dates en général, et des fuseaux horaires en particulier, y compris pour le formatage. Et ça évite de manipuler pytz à la main.

Et pour le reste… bonne chance !

Programmation orientée objet

Souvenez-vous, Python a des méthodes magiques. 3 sont dédiées au formatage.

__repr__ est utilisée quand on appelle repr() sur un objet. Typiquement, c’est ce qui s’affiche dans le shell si on utilise pas print(). C’est aussi ce qui détermine la représentation d’un objet quand on affiche une collection qui le contient.

__str__ est utilisée quand on appelle str() sur un objet. Quand on fait print() dessus par exemple. Si __str__ n’existe pas, __repr__ est appelée.

__format__ est utilisée quand on passe cet objet à format(), ou que cet objet est utilisé dans une f-string.

Ex :

class Foo:
    def __repr__(self):
        return "<Everybody's kung foo fighting>"
    def __str__(self):
        return "C'est l'histoire d'un foo qui rentre dans un bar"
    def __format__(self, age):
        if int(age or 0) > 18:
            return "On s'en bat les couilles avec une tarte tatin. Tiède."
        return "On s'en foo"

Ce qui donne :

>>> Foo()
<Everybody's kung foo fighting>
>>> print(Foo())
C'est l'histoire d'un foo qui rentre dans un bar
>>> print([Foo(), Foo()])
[<Everybody's kung foo fighting>, <Everybody's kung foo fighting>]
>>> "J'ai envie de dire: {}".format(Foo())
"J'ai envie de dire: On s'en foo"
>>> f"J'ai envie de dire: {Foo():19}"
"J'ai envie de dire: On s'en bat les couilles avec une tarte tatin. Tiède."

Astuce de dernière minute

Enfin pour conclure cet article dont la longueur n’a d’égale que celle de la période entre deux publications sur le blog, une petite remarque.

S’il est certes courant de formater une string, il est aussi possible de déformer un string. Ce sont des pièces plus résistantes qu’il n’y parait, et en cas d’empressement, le retrait total n’est pas nécessaire :

String en levrette

Ne pas porter de strings du tout évite aussi tout une classe de bugs

Assurez-vous juste que la partie ficelle soit suffisament éloignée pour éviter les frictions fort désagréables quand on entame un algo avec une grosse boucle.

Sinon, moins intéressant, mais toujours utile, les strings en Python peuvent êtres écrites sur plusieurs lignes de plusieurs manières:

>>> s = ("Ceci est une chaine qui n'a pas " 
...      "de saut de ligne mais qui est "
...      "écrite sur plusieurs lignes")
>>> print(s)
Ceci est une chaine qui n'a pas de saut de ligne mais qui est écrite sur plusieurs lignes

Cela fonctionne car deux chaînes littérales côte à côte en Python sont automatiquement concaténées au démarrage du programme. Cela évite les + \ à chaque fin de ligne, pourvu qu’on ait des parenthèses de chaque côté de la chaîne.

L’alternative des triples quotes est assez connue:

>>> s = """
...    Ceci est une chaine avec des sauts de lignes
...    écrite sur plusieurs lignes.   
... """
>>> print(s)
 
    Ceci est une chaine avec des sauts de lignes
    écrite sur plusieurs lignes.

Pour éviter l’indentation et les espaces inutiles:

>>> from textwrap import dedent
>>> print(dedent(s).strip())
Ceci est une chaine avec des sauts de lignes
écrite sur plusieurs lignes.

Perso j’ai un wrapper pour ça.

Un tools d’itertour, ou l’inverse 21

samedi 29 octobre 2016 à 22:26

Ah, ça faisait longtemps que je ne vous avais pas fait un petit article bien long. Ca manquait d’opportunité de partager de la musique. Allez hop !

Python est un langage avec un “for” penchant pour l’itération si je puis dire. Et ce n’est que justice qu’il ait donc un module dédié pour les opérations d’itération : itertools.

itertools est un peu impénétrable pour quelqu’un qui n’a pas l’habitude, alors je vais vous faire un petit tour du propriétaire.

Principes de base

Itertools a pour but de manipuler les itérables, et si vous voulez en savoir plus sur le concept d’itérabilité, je vous renvoie à l’article sur les trucmuchables. Pour faire court, les itérables sont tout ce sur quoi on peut appliquer une boucle for.

Le problème, c’est que tous les itérables ne se comportent pas de la même façon. En effet, certains ont une taille définie:

>>> len("abcd")
4
>>> len(range(3))
3

D’autres non:

>>> len(open('/etc/fstab'))
Traceback (most recent call last):
  File "<ipython-input-7-baed595d06b3>", line 1, in <module>
    len(open('/etc/fstab'))
TypeError: object of type '_io.TextIOWrapper' has no len()

Certains, se terminent:

>>> a = list(open('/etc/fstab'))
>>>

D’autres sont infinis:

>>> def infinite_generator():
...     while True:
...         yield 1
...
>>> list(infinite_generator) # bloque la VM jusqu'à crasher sur un MemoryError

Du coup, si vous voulez faire des opérations qui marchent sur tous les itérables, il faut écrire des algos spéciaux dit “paresseux” (en anglais, “lazy”), c’est à dire qui font le travail minimum. Ils se lancent à la dernière minute et ne lisent rien de plus que nécessaire.

Et c’est ce que contient itertools. Les fonctions de ce module marchent sur les fichiers, les générateurs, les sockets réseaux comme les listes, les tuples et les chaînes de caractères.

chain

Combine plusieurs itérables en un seul:

>>> from itertools import chain
>>> generator = chain("abc", range(3), [True, False, None])
>>> generator
<itertools.chain object at 0x7f2ea903d550>
>>> list(generator)
['a', 'b', 'c', 0, 1, 2, True, False, None]

Très utile pour boucler sur des itérables hétérogènes comme si c’était une seule et même structure. Chaîner des fichiers, des générateurs, etc.

islice

Comme la syntaxe de slicing [::]:

>>> carres_de_nombres_pairs = [x * x  for x in range(100) if x % 2 == 0]
>>> carres_de_nombres_pairs[3:11]
[36, 64, 100, 144, 196, 256, 324, 400]

Mais marche sur tous les itérables, même sur les générateurs:

>>> carres_de_nombres_pairs = (x * x  for x in range(100) if x % 2 == 0)
>>> carres_de_nombres_pairs
<generator object <genexpr> at 0x7f2eab171a98>
>>> from itertools import islice
>>> generator = islice(carres_de_nombres_pairs, 3, 11)
>>> generator
<itertools.islice object at 0x7f2eab1a26d8>
>>> list(generator)
[36, 64, 100, 144, 196, 256, 324, 400]

cycle

Boucler de manière infinie sur un itérable. Quand on arrive à la fin, on revient au début.

>>> generator = cycle('abc')
>>> generator
<itertools.cycle object at 0x7f2eab59a588>
>>> next(generator)
'a'
>>> next(generator)
'b'
>>> next(generator)
'c'
>>> next(generator)
'a'

C’est très pratique, mais assez dangereux donc faites des tests avant de l’utiliser. En effet si l’itérable génère les données à la volée, elles sont stockées dans un buffer, donc ça charge tout en mémoire. De plus, c’est une boucle infinie. Ne castez pas ça avec un tuple ou une liste :)

repeat

Prend n’importe quel élément, et le retourne encore et encore.

>>> generator = repeat("plop") # répète infiniment
>>> next(generator)
'plop'
>>> next(generator)
'plop'
>>> next(generator)
'plop'
>>> generator = repeat("plop", 2) # répète 2 fois
>>> for x in generator:
...     print(x)
...
plop
plop

On ne peut pas passer un callable, mais iter() le fait déjà. La signature normale de cette dernière prend un iterable, mais si on lui passe deux paramètres, un callable et un sentinel, on obtient un générateur qui va appeler le callable jusqu’à ce qu’il retourne une valeur égale au sentinel.

Exemple, vous voulez lire un fichier 50 caractères à la fois. On passe une fonction qui retourne les 50 caractères suivants (le callable) et une chaîne de caractères vide (le sentinel):

>>> f = open('/etc/fstab')
>>>
>>> def read_50_chars():
...     return f.read(50)
...
>>> generator = iter(read_50_chars, '') # lit 50 caractères à la fois jusqu'à tomber sur ''
>>> generator
<callable_iterator object at 0x7f2eab5abef0>
>>> next(generator)
'# /etc/fstab: static file system information.\n#\n# '
>>> next(generator)
"Use 'blkid' to print the universally unique identi"
>>> next(generator)
'fier for a\n# device; this may be used with UUID= a'

iter() est un built-in, pas dans itertools, mais ça aurait été con de pas en parler.

zip_longest

Comme zip(), mais au lieu de s’arrêter quand le premier itérable est fini:

>>> list(zip('abc', range(5)))
[('a', 0), ('b', 1), ('c', 2)]

On remplit les valeurs manquantes:

>>> list(zip_longest('abc', range(5)))
[('a', 0), ('b', 1), ('c', 2), (None, 3), (None, 4)]
>>> list(zip_longest('abc', range(5), fillvalue="Tada !"))
[('a', 0), ('b', 1), ('c', 2), ('Tada !', 3), ('Tada !', 4)]

starmap

Comme map(), mais applique l’opérateur splat sur les arguments avant.

Si map() ressemble (en mieux) à:

def map_en_moins_bien(callable, iterable):
    for x in iterable:
       yield callable(x)

starmap() fait:

def map_en_moins_bien(callable, iterable):
    for x in iterable:
       yield callable(*x)

Exemple:

>>> nombres = [(1, 2), (3, 4), (5, 6)]
>>> def ajouter(a, b):
...     return a + b
...
>>> from itertools import starmap
>>> list(starmap(ajouter, nombres))
[3, 7, 11]

Ces fonctions sont moins utilisées à cause des listes en intension. Mais il y a des trucs rigolos comme:

>>> list(starmap(print, zip(range(3), map(int, "123"))))
0 1
1 2
2 3
[None, None, None]

Ouais, vous allez pas utiliser ça tous les jours :)

filterfalse

Le contraire de filter(), garde les résultats négatifs au lieu des résultats positifs:

>>> list(filter(est_pair, range(10)))
[0, 2, 4, 6, 8]
>>> from itertools import filterfalse
>>> list(filterfalse(est_pair, range(10)))
[1, 3, 5, 7, 9]

Comme précédemment, aujourd’hui on utilise peu ces fonctions car on a les listes en intension. Mais certaines astuces sont sympas comme:

>>> list(filter(bool, [True, False, 1, 0, 'foo', '']))
[True, 1, 'foo']
>>> list(filterfalse(bool, [True, False, 1, 0, 'foo', '']))
[False, 0, '']

groupby

Ah, groupby(), la fonction la moins facile à comprendre. D’abord, personne ne lit la doc, et la doc dit clairement qu’il faut trier les éléments de l’itérable AVANT d’appliquer groupby, sinon les résultats sont incohérents.

Ensuite, groupby() peut prendre un callback pour un usage avancé, et toutes les fonctions qui prennent un callback sont plus dures à comprendre. Mais pour que ça ait du sens, il faut que le tri préalable s’applique sur le même callback. Personne ne pige jamais ça.

Enfin, groupby() ne renvoie pas les éléments directement, mais des objets “groupers”, qui sont eux mêmes des générateurs. Tout est bien fait pour embrouiller le chaland, surtout pour une fonction qu’on utilise pas souvent.

Pour comprendre groupby(), le mieux est de commencer par l’entrée et la sortie. Vous avez un truc comme ça:

>>> noms = ['Zebulon', 'Léo', 'Alice', 'Bob', 'Anaïs', 'Adam', 'Bernardo', 'Io']

Et vous voulez les grouper par un critère, un peu comme un GROUP BY en SQL. Ca peut être n’importe quoi:

Prenons un exemple simple, et groupons les par le nombre de lettres qu’ils contiennent. Ca donnerait quelque chose comme ça:

[(2, ('Io',)),
 (3, ('Léo', 'Bob')),
 (4, ('Adam',)),
 (5, ('Alice', 'Anaïs')),
 (7, ('Zebulon',)),
 (8, ('Bernardo',))]

Pour obtenir ce résultat avec groupby(), il faut d’abord trier l’itérable (ici notre liste) par nombre de lettres:

>>> noms.sort(key=len)
>>> noms
['Io', 'Léo', 'Bob', 'Adam', 'Alice', 'Anaïs', 'Zebulon', 'Bernardo']

Si vous ne vous souvenez plus comment fonctionne le tri en Python, particulièrement le paramètre key, allez vite lire l’article dédié du lien précédent. En effet le mécanisme est le même pour groupby() donc il faut comprendre ce fonctionnement de toute manière.

Ensuite on applique groupby(), avec la même fonction key:

>>> noms = groupby(noms, key=len)
>>> noms
<itertools.groupby object at 0x7f19c4461778>

A ce stade, on a un générateur qu’on peut parcourir, mais il ne contient pas le résultat tel que je vous l’ai montré. A la place, il yield des paires (group, grouper):

>>> next(noms)
(2, <itertools._grouper object at 0x7f19c48c0080>)

Le premier élément, le groupe, est ce que votre fonction key retourne, c’est à dire ce sur quoi vous vouliez grouper. Ici la taille du mot.

En second, un objet grouper, qui est un générateur yieldant les mots qui correspondent à ce groupe.

Donc pour vraiment voir le contenu de tout ça, il faut se taper une double boucle imbriquée:

>>> from itertools import groupby
>>> for groupe, groupeur in groupby(noms, key=len):
...     print(groupe, ":")
...     for mot in groupeur:
...         print('-', mot)
2 :
- Io
3 :
- Léo
- Bob
4 :
- Adam
5 :
- Alice
- Anaïs
7 :
- Zebulon
8 :
- Bernardo

La raison de ce manque d’intuitivité, c’est que théoriquement groupby() peut travailler de manière paresseuse et donc les groupeurs sont des générateurs qui traitent l’arrivée des données au fur et à mesure. Mais c’est rare d’avoir des données lazy qui arrivent déjà pré-ordonnées. Ensuite ça suppose que vous voulez lire ces données au fur et à mesure, mais généralement le jeu de données groupées n’est pas suffisamment grand pour que ça soit un vrai problème et on caste directement le groupe en tuple ou en liste.

Dans la pratique donc, groupby() fait parti de ces fonctions un peu overkill, car un usage typique ne bénéficiera pas du côté paresseux. En tout cas ça ne m’est jamais arrivé.

dropwhile

dropwhile() prend un callable et un itérable. Le callable est appelé sur chaque élément, et tant qu’il retourne quelque chose de vrai, l’élément est ignoré. C’est un peu tordu car ça demande d’écrire sa fonction de filtre à l’inverse de ce qu’on veut.

Par exemple, si vous voulez commencer à lire un fichier à partir de la première ligne qui commence par un commentaire, il faut que votre fonction retourne True… si la ligne ligne ne commence PAS par un commentaire.

>>> def ne_commence_pas_par_un_commentaire(element):
...     return not element.startswith('#')
...
>>> generator = dropwhile(ne_commence_pas_par_un_commentaire, open('/etc/fstab'))
>>> generator
<itertools.dropwhile object at 0x7f19c450d688>
>>> next(generator)
'# /etc/fstab: static file system information.\n'

dropwhile veut dire “ignorer tant que”. Donc ici on ignore tant que la ligne ne commence pas par un commentaire.

takewhile

takewhile() est le contraire de dropwhile(), on prend les éléments tant qu’une condition est vraie, et on s’arrête dès que la condition n’est plus vraie. On les utilise souvent en combinaison d’ailleurs, pour slicer les itérables en utilisant des conditions plutôt que des indexes.

Comme pour dropwhile, il faut penser à l’envers. Vous voulez vous arrêter quand un nombre dépasse 10000 ? Alors il faut faire une fonction qui retourne True tant que le nombre ne dépasse PAS 10000. Jusqu’ici j’ai utilisé des fonctions traditionnelles, mais bien entendu, une lambda marche aussi:

>>> nombres = (x * x for x in range(1000000000000))
>>> from itertools import takewhile
>>> generator = takewhile(lambda x: x < 10000, nombres)
>>> next(generator)
0
>>> next(generator)
1
>>> next(generator)
4
>>> list(generator)[-1]
9801

count

count() est une relique du passé, qui fait ce que fait range() maintenant, mais en moins bien. Vous pouvez l’ignorer.

tee

tee() est une fonction très intéressante qui duplique votre itérable en autant de clones que vous le souhaitez. Pour ce faire, tee() garde en mémoire les derniers éléments générés, ce qui fait que si vous lisez les clones les uns après les autres, ça n’a aucun intérêt. Autant caster en liste et lire la liste plusieurs fois.

Non, l’interêt de tee() est si vous lisez les clones en parallèle. Car là, tee() ne garde en mémoire que les éléments qui n’ont pas été lus par le dernier clone utilisé.

Par exemple, ceci est inutile:

>>> from itertools import tee
>>> clone1, clone2, clone3 = tee((x for x in range(100)), 3)
>>> list(clone1)[:3]
[0, 1, 2]
>>> list(clone2)[:3]
[0, 1, 2]

Par contre ceci économise pas mal de mémoire:

>>> clone1, clone2, clone3 = tee((x for x in range(100)), 3)
>>> next(clone1)
0
>>> next(clone2)
0
>>> next(clone3)
0
>>> next(clone1)
1
>>> next(clone2)
1
>>> next(clone3) # la valeur 0 n'est plus en mémoire après ça
1

compress

compress() est une curiosité qui va surtout parler aux data-scientists et matheux, et d’une manière générale aux adeptes de numpy.

Cette fonction prend deux itérables, un avec des valeurs à filtrer, et un avec des valeurs qui indiquent s’il faut filtrer l’élément ou non:

>>> list(compress(['toto', 'tata', 'titi'], [True, False, True]))
['toto', 'titi']

Je n’ai pas trouvé de use case pour cette fonction dans ma vie de tous les jours. Si quelqu’un est inspiré en commentaire, je mettrai l’article à jour.

accumulate

accumulate() est un pur produit de la programmation fonctionnelle. C’est un peu comme un map(), mais au lieu de passer chaque élément au callable, on lui passe l’élément, et le résultat du dernier appel.

Par exemple, vous voulez multiplier tout une liste : [1, 2, 3, 4]

Vous allez faire:

(((1 * 2) * 3) * 4)

C’est typiquement pour ce genre de chose qu’on utiliserait la fonction reduce():

>>> reduce(lambda x, y: x * y, [1, 2, 3, 4])
24

accumulate() fait cela, mais en plus vous yield tous les résultats intermédiaires:

>>> list(accumulate([1, 2, 3, 4], lambda x, y: x * y))
[1, 2, 6, 24]

La signature est inversée par rapport à reduce(), c’est ballot. Dans d’autres langages vous trouverez ce genre de fonction sous le nom fold() ou scan(), mais c’est le même principe.

product

product() prend des itérables, et vous balance toutes les combinaisons des éléments de ces itérables. On s’en sert surtout pour éviter les boucles for imbriquées. Par exemple au lieu de faire:

>>> lettres = "abcd"
>>> chiffres = range(3)
>>> for lettre in lettres:
...     for chiffre in chiffres:
...         print(lettre, chiffre)
...
a 0
a 1
a 2
b 0
b 1
b 2
c 0
c 1
c 2
d 0
d 1
d 2

On va faire:

>>> from itertools import product
>>> for lettre, chiffre in product(lettres, chiffres):
...     print(lettre, chiffre)
...
a 0
a 1
a 2
b 0
b 1
b 2
c 0
c 1
c 2
d 0
d 1
d 2

combinations

Génère tous les combinaisons possibles des éléments d’un itérable:

>>> from itertools import combinations
>>> list(combinations('abcd', 2))
[('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'c'), ('b', 'd'), ('c', 'd')]
>>> list(combinations('abcd', 3))
[('a', 'b', 'c'), ('a', 'b', 'd'), ('a', 'c', 'd'), ('b', 'c', 'd')]

combinations_with_replacement

Pareil, mais autorise les doublons:

>>> from itertools import combinations_with_replacement
>>> list(combinations_with_replacement('abcd', 3))
[('a', 'a', 'a'), ('a', 'a', 'b'), ('a', 'a', 'c'), ('a', 'a', 'd'), ('a', 'b', 'b'), ('a', 'b', 'c'), ('a', 'b', 'd'), ('a', 'c', 'c'), ('a', 'c', 'd'), ('a', 'd', 'd'), ('b', 'b', 'b'), ('b', 'b', 'c'), ('b', 'b', 'd'), ('b', 'c', 'c'), ('b', 'c', 'd'), ('b', 'd', 'd'), ('c', 'c', 'c'), ('c', 'c', 'd'), ('c', 'd', 'd'), ('d', 'd', 'd')]

permutations

Comme combinations, mais en générant aussi les mêmes combinaisons dans un ordre différent:

>>> list(permutations('abc', 3))
[('a', 'b', 'c'), ('a', 'c', 'b'), ('b', 'a', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b'), ('c', 'b', 'a')]

Puis-je copier/coller mon brave ? 6

dimanche 23 octobre 2016 à 09:41

Dans 0bin on utilise encore flash pour faire le copier/coller, et je pense qu’on va le virer. Plus de raison de supporter les navigateurs trop vieux et après tout c’est pas grave de se voir refuser un raccourci pour copier/coller : le reste est utilisable.

Pourquoi je vous dis ça ?

Et bien parce que sebsauvage a partagé un snippet pour utiliser l’API du clipboard en JS. Et j’ai donc voulu savoir comment détecter que cette functionalité est implémentée par le navigateur en cours.

Je suis ainsi allé voir les sources de modernizr, et il implémente en fait une combinaisons de 2 techniques.

D’abord, vérifier si l’objet window a un attribut ClipboardEvent. Si oui, c’est réglé. Si non, il crée un div, on check s’il a un attribut paste. Le reste sont les hacks de compatibilité avec les très vieux navs que je ne vais pas retranscrire ici.

Donc en gros, avant de faire un copier/coller en JS, vérifiez:

function implementClipboardAPI(){
   try {
     return (!!window.ClipboardEvent || 'onpaste' in document.createElement('div'));
   } catch(e) {
     return false;
   }
}