PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

git revert pour annuler proprement le dernier commit

mercredi 14 novembre 2012 à 16:18

On ne peut pas à proprement parler supprimer un commit de Git. Mais on peut l’inverser proprement.

Assurez-vous d’avoir une copie de travail propre (sans files to be commited ou conflits, quitte à stasher). Puis:

git revert HEAD~1

Cette commande va immédiatement prendre le précédent commit, créer une commit exactement inverse, et l’appliquer.

Si vous aviez: C1 — C2 — C3

Et que vous vouliez annuler C3.

Vous allez vous retrouver avec: C1 — C2 — C3 — C4.

Avec C4 comme exacte inverse de C3.

Si vous voulez annuler plus d’un commit: git revert HEAD~nombre-de-commits

Y a plus qu’à pusher !

Ceci est complètement différent de git commit --amend qui lui remplace le commit précédent par le commit actuel à condition de ne pas avoir pushé le commit précédent.

Avec amend, si vous aviez:

Si vous aviez: C1 — C2 — C3

Et que vous vouliez annuler C3.

Vous allez vous retrouver avec: C1 — C2 — C4

Mais il faut créer C4 de toute pièce.

revert, c’est comme Cabrel: c’était mieux avant.
amend, c’est comme Britney: ooups, I did it again.

Shut ! J’ai un secret en Python…

mardi 13 novembre 2012 à 16:11

J’adore les chasses au trésor. Préparer les énigmes, suivre les participants, c’est bien plus marrant que de faire partie d’une des équipes. Je me suis toujours dit que si je devenais obscènement riche, j’achèterais 10 millions d’euros en pièces d’or et je les planquerais quelque part, puis je lancerais une chasse au trésor mondiale pour le fun. Des années de préparation, des années d’amusement !

Une des problématiques de la chasse au trésor, c’est de s’assurer que tout le monde a une chance. Il faut varier les épreuves: musique, littérature, observation, orientation, histoire, puzzle, exercice physique, etc. Et bien sûr il faut créer un système tolérant à l’échec, car le but c’est quand même que quelqu’un trouve le butin. La meilleure partie est après tout ce très court moment d’exultation où on ouvre le coffre, ou son équivalent.

Quand c’est une chasse au trésor pour mioches, c’est facile, il suffit de donner un temps d’avance à l’équipe qui réussi une épreuve en premier, de balancer un indice, de retirer une contrainte, etc. Au pire on saute juste une épreuve.

Dans une grosse chasse au trésor, il faut faire quelque chose de plus sérieux: on doit limiter son interaction au maximum avec les équipes pour leur donner une sensation d’aventure, et de challenge.

La solution à ce problème, c’est d’utiliser des indices divisibles:

Ainsi, si on a 5 épreuves, on prend l’indice suivant, on le divise en 5, et on s’assure que réussir 3 épreuves suffisse à donner assez d’informations pour reconstituer l’indice suivant. Ca marche avec les cartes incomplètes, les textes à trou, les dessins superposables, etc.

En informatique il existe un moyen de faire cela avec le partage de clé secrète de Shamir. Et bien sûr, comme toujours, il y a une lib Python qui permet de s’en servir.

Malheureusement, comme beaucoup de libs scientifiques, elle est bien chiante à installer et elle ne respecte pas le PEP8:

D’abord il vous faut installer pycrypto, une dépendance:

pip install pycryto

Assurez-vous d’avoir les headers de Python (paquet python-dev sous Ubuntu), car c’est une extension en C que pip va compiler.

Ensuite, on doit cloner le repo à la main (si, si):

git clone https://code.google.com/p/python-secret-sharing/

Et copier le dossier nommé ‘ss’ (un truc aussi strict, c’est forcément les nazis j’vous dis) dans le dossier de votre application ou dans votre dossier de site-package. Si j’ai le temps je leur proposerais un petit setup.py.

Ensuite ça s’utilise très facilement:

>>> from ss.secret_sharing import secret_sharing
>>> secret = secret_sharing("1 - voler des caleçons. 2 - .... 3 - profit !", 20, 3)

Ceci va produire un secret divisible en 20 parts, donc il ne faut que 3 parts pour relire le secret de conquête du monde que j’ai planqué dans cette citation pleine de sagesse.

Je peux obtenir une part au hasard:

>>> secret.share_random() 
((16, 721654044499788854347597992565674L),
 (18,
  102214856329848326909206303519239813304695812929767500268700035275955811306374521388L))

Notez que c’est un simple tuple. On peut en faire des choses avec un simple tuple, comme le serialiser, le compresser, l’obfusquer, l’encoder, le sténographier…

Et si je veux retrouver le secret:

>>> from ss.secret_sharing_c import secret_sharing_c
>>> assembleur = secret_sharing_c()
>>> assembleur.add_share(secret.share_random())
>>> assembleur.add_share(secret.share_random())
>>> assembleur.add_share(secret.share_random())
>>> print assembleur.reconstruct()
1 - voler des caleçons. 2 - .... 3 - profit !

Attention: si on met moins ou plus de parts dans l’assembleur que prévu, il s’auto-poutre avec un salami en bêton bardé de ZeroDivisionError ou OverflowError.

Bref, comme d’habitude l’informatique peut être fun, fun, fun. Je me demande pourquoi on enseigne jamais ça en cours. C’est quand même plus marrant que de créer des fonctions de manipulation de graphes en Java comme on avait eu la bonne idée de nous faire faire.

Valeurs et références en Python

lundi 12 novembre 2012 à 20:28

Petit article en complément de l’article de Réchèr.

Il y a plusieurs manières de passer une variable en informatique: par valeur ou par référence. Et dans les langages bas niveau comme le C, on se pose la question: “passe-t-on la valeur ? un pointer ? un pointer vers un pointer ?”

En Python ce n’est pas la question puisque tout se passe par référence. Tous les objets. Dans tous les cas.

La question est donc plutôt: “ça veut dire quoi passer par référence ?”

Assignation et référence

Une référence est comme une adresse qui dit où la donnée se trouve en mémoire.

Quand vous faites ceci:

a = [1, 2, 3]

Vous n’assignez pas la liste à la variable “a”, vous assignez une référence vers la liste, donc une sorte d’adresse qui indique où elle se trouve en mémoire. (en vérité vous n’assignez rien, les variables sont des étiquettes en Python, mais on va ignorer ce détail ici).

Et quand vous faites:

print a

Python va chercher dans a la référence, et retrouver la liste en suivant “l’adresse”.

C’est important car ça veut dire deux choses.

1. Quand vous faites ça:

a = b

Vous ne copiez pas la liste. Vous copiez la référence. Du coup, on ne prend pas deux fois la place en mémoire, et la copie est très rapide

2. Quand vous faites ça:

a = b

Vous ne copiez pas la liste. Vous copiez la référence. Bis.

Et si vous faites:

b.append(4)

Alors:

print a

Va donner…

[1, 2, 3, 4]

Car en faisant append() sur b, Python va trouver une référence, retrouver la liste derrière la référence, et faire un append() sur la liste. Comme c’est la même réference dans a et b (puisqu’on l’a copié), c’est la même liste derrière. Tout ce qu’on applique à a, s’applique donc à b, et vice-versa.

Si vous voulez faire une vraie copie, alors il faut recréer une toute nouvelle liste. Par exemple:

b = list(a)

Mutable et non mutable

L’assignation par référence n’a vraiment d’importance que dans le cas où un objet est mutable. En Python, il existe en effet deux types d’objets: les mutables (listes, dictionnaires, sets, objets custo, etc) et les nons mutables (strings, int, floats, tuples, etc).

Les mutables sont ceux qu’on peut modifier après leur création. Les non mutables sont ceux qu’on ne peut pas modifier après création.

On ne peut pas modifier 152 un fois que l’objet int est créé. Mais on peut rajouter des éléments à une liste après qu’elle soit créé. Les ints sont non mutables. Les lists sont mutables.

Cela peut surprendre, mais les strings sont non mutables. Même quand vous faites:

>>> pa = "papa"
>>> pi = pa.replace("a", "i")
>>> print pa
papa
>>> print pi
pipi

Vous ne modifiez pas la chaîne originale: vous créé une copie de la chaîne, et la chaîne de départ n’a pas bougé.

C’est important car si on fait une référence vers un type non mutable, on s’en oint le pourtour anale avec pelle à tarte: il ne peut pas être modifié. Peu importe que la référence soit copiée à droite et à gauche. Mais si l’objet est mutable, chaque copie de la référence ajoute un endroit dans le programme duquel on peut modifier l’objet.

Cela a des implications parfois assez coquines.

Ainsi:

>>> l = [0] * 3
>>> l # une liste de 3 zéros
[0, 0, 0]
>>> l[1] += 1
>>> l
[0, 1, 0]

Ici tout se comporte comme prévu. Les ints sont non mutables, donc on ne s’aperçoit pas d’un détail important: [0] * 3 copie la référence à 0 trois fois. Quand on fait +=, ça remplace l’ancien int par un nouveau, donc un seul item de la liste est changé.

Mais si on fait vicieusement:

>>> l = [[0]] * 3
>>> l # une liste de listes d'un seul zéro chacune
[[0], [0], [0]]
>>> l[1][0] += 1
>>> l
[[1], [1], [1]]

Ici on a copié 3 fois la référence vers la même liste. Du coup une modification affecte les 3 items. Doh.

Rappels: les tuples ne sont pas mutables. Et on peut passer d’un tuple à une liste avec tuple(l) et list(t). Pensez-y si vous rencontrez ce genre de problème.

Passage des arguments de fonction par référence

Quand vous faites cela:

def encore_une_fonction_d_exemple_inutile(l):
    l.append(4)
    return l
 
>>> l1 = [1, 2, 3]
>>> encore_une_fonction_d_exemple_inutile(l1)
>>> print l1
[1, 2, 3, 4]

Vous noterez que la liste a été modifiée. C’est parce que l’on passe une référence à la liste quand on la passe en argument. Toute modification de la liste dans la fonction est donc visible en dehors de la fonction. Si le paramètre était immutable, encore une fois on s’en ficherait. Mais là, comme liste est mutable, notre fonction possède ce qu’on nomme un effet de bord: quand on l’appelle, elle a des conséquences sur des objets qui existe en dehors d’elle même.

Il est généralement de bon ton d’éviter les effets de bord, aussi, essayez toujours de travailler sur des copies: utilisez le slicing, les listes en intention, les générateurs, etc., pour retourner les nouvelles valeurs plutôt que de modifier l’objet original.

Ici:

def encore_une_fonction_d_exemple_inutile(l):
    return l1 + [4]

Nous permet de retourner une nouvelle liste. Si la liste prend beaucoup de place en mémoire, on peut faire ça:

def encore_une_fonction_d_exemple_inutile(l):
 
    for x in l:
        yield x
    yield 4

Ce qui aurait pour effet de retourner un itérable similaire, sans prendre plus de mémoire.

Valeurs par défaut et référence

Quand on utilise une valeur par défaut, par exemple dans la déclaration des paramètres d’une fonction, on initialise une référence qui va rester la même pour toute la durée du programme.

def encore_une_fonction_d_exemple_inutile(l=[1, 2, 3]):
    l.append(4)
    return l
 
>>> encore_une_fonction_d_exemple_inutile()
>>> encore_une_fonction_d_exemple_inutile()
>>> print l1
[1, 2, 3, 4, 4]

On constate ici que 4 a été ajouté deux fois dans la liste. En effet, l est l’argument par défaut, et ici, il est initialisé à la référence pointant sur une liste [1, 2, 3]. Pas sur la valeur [1, 2, 3]. [1, 2, 3] est stocké quelque part dans un monde invisible que seul Guido Van Rossum connait, et l contient juste la référence à cette liste.

Cette référence est gardée, et à chaque appel, c’est la même liste qui est utilisée. Si on appelle deux fois la fonction, la première fois c’est la liste [1, 2, 3, 4] à laquelle on ajoute 4. Puis comme c’est la même référence, on ajoute ensuite 4 à la même liste, à laquelle on avait déjà ajouté 4.

Bref, évitez les mutables dans les paramètres par défaut à moins de savoir ce que vous faites (cache ou memoization).

Variables de classe et références

La même problématique existe pour le classes. Si vous faites:

class AuChocolat(objet):
 
    suppéments = ['chantilly', 'praline']
 
>>> g1 = AuChocolat()
>>> g2 = AuChocolat()
>>> g1.supplements.pop()
'praline'
>>> g2.supplements
['chantilly']

Même principe: toute variable de classe pointe pour toute la durée du programme sur la même réference. La référence va être partagée entre toutes les instances de la classe. C’est donc la même liste !

Maintient d’une référence

Python garde un objet en mémoire tant qu’il existe une référence vers cet objet.

>>> a = 1
>>> b = a
>>> del a
>>> print b
1

Si je supprime a, il reste b, donc l’objet est toujours en mémoire. Si je supprime b également, Python va lancer le processus de nettoyage pour effacer l’objet de la mémoire.

Une référence existe toujours dans le cadre d’un scope. Par exemple, si une référence est copiée dans une fonction, quand la fonction se termine, la copie de la référence est supprimée, et ne compte plus comme une voix pour le maintient de l’objet en mémoire.

En Python, il n’existe donc aucun moyen de supprimer un objet. On peut juste supprimer toutes ses références, et attendre que Python s’apperçoive que tout le monde s’en branle à présent de l’objet, et le supprime.

Dans certains cas particuliers, on veut qu’une référence à un objet ne compte pas comme une voix. On peut le faire en utilisant le module weakref, mais cela ne marche qu’avec les classes que l’on code soit-même.

class ALaVanille(object):
    pass
 
>>> import weakref
>>> g1 = ALaVanille()
>>> g2 = weakref.proxy(g1)
>>> g2
<weakproxy at 0x7f5f26994730 to ALaVanille at 0x7f5f26992d90>
>>> g1.foo = "bar"
>>> g2.foo
'bar'
>>> del g1
>>> g2
<weakproxy at 0x7f5f26994730 to NoneType at 0x859380>

Dès qu’on supprime g1, g2 proxy vers None, car la référence vers l’instance de ALaVanille ne compte pas pour garder en vie l’objet.

Les abréviations dans le domaine adulte, vous connaissez ?

lundi 12 novembre 2012 à 00:44

Avec tous ces nouveaux riches – et oui il n’y en a jamais eu autant – les sites d’escorts ont poussé comme des champignons, de même que les forums spécialisés dans le recenssement des lieux à filles de joies.
Et ça tombe bien c’est la saison des champignons ! Alors voici un petit guide des abréviations que vous risquez de trouver sur ces champig…sites quand vous serez riches et que vous comprendrez que les boudins de Meetic ne servent à rien (pour un riche) et que vous chercherez de la bombe atomique comme Aria Giovani.

A
ATMAss To MouthDans le cul puis dans la bouche
B
BarebackWithout condom.
Sans capote !
BBBJBare Back Blow JobTurlute sans capote
BBWBig Beautiful Woman
Sauvez Willy
BDSMBondage, Discipline, Sado-Masochism.
je te fouette, tu me fouettes..
BFEBoy Friend Experience
Toi t'es un pote...
BJBlow JobTurlutte
BLSBall Licking and Sucking
Voir ici
Brown Shower Defecation on your partner.
se faire chier dessus
BSBody slidemassage à l'huile
C
CBJCovered Blow JobPipe avec capote
CFMCome Fuck MeSe dit d'une fille qui sent le cul
CIMCum in Mouth
Une bonne giclée de sperme dans la bouche
COBCum on Breast
Ejac sur les nibards
COFCum on FaceEjaculation faciale
D
DFKDeep French Kissing
Avec la langue régis ! Et bien profond
DSDoggyStyle. Man behind girl, girl on hands and kneesMon petit poney...
DPADouble Penetration Anal
2 bites dans le cul
DTDeep Throat
Gorge profonde
F
FJFoot jobbranlette avec les pieds
FKFrench KissingAvec la langue
FKKFrei Körper Kultur Maisons closes fleurissants aux abords des routes allemandes...
FSFull Service Je vous fait le parebrise avec ça ?
G
GFEGirl Friend Experience
C'est comme si t'étais ma meuf, tu me coûte autant mais au moins je te baise.
GSGolden Showerse faire pisser dessus
H
HEHappy EndingMassage avec finition
HJHand Job
Branlette à la main
L
LTLong TimeRelation d'au moins 24 heures avec une Biatch. Ex: Kany West est en LT avec kim Kardashian
O
OWOOral WithOut a condomTurlutte sans capote, voir BBBJ
OWOTCOral Without Condom To Completion
Turlutte sans capote avec éjac
S
SOMFSat On My Face
voir la dernière image de ça
STShort TimePrendre une Biatch pour 1 heure et la remercier.
T
Tossing SaladAnalingusse faire bouffer le cul
TSTransexualMon frère depuis que je l'appelle ma soeur
TUMATongue Up My Ass
Se faire mettre la langue dans le trou de balle, en général quand on ose pas embrasser.
W
WFWild FuckBaise déjantée

J’ai du en oublier quelques unes mais en gros avec ça on s’en sort déjà pas mal au moment de choisir le dessert.

Et N’oubliez pas la célèbre citation de George Simenon :
“Les filles que l’on paie ne sont pas celles qui nous coûtent le plus cher…”

id(), None et bidouilleries mémoire en python.

samedi 10 novembre 2012 à 18:37

Ceci est un post invité de Réchèr posté sous licence creative common 3.0 unported.

Mon maître-ninja python, entre deux riffs sur sa contrebasse électrique, m’avait un jour dit : “il ne faut pas écrire if a == None:, mais if a is None:“. Il m’avait ensuite donné une justification pertinente, que je n’ai pas retenue, car j’étais en train de penser à des nichons. Puis il avait conclu par “on n’est pas égal au vide. On EST le vide.”

Rien que pour vous, ainsi que pour m’auto-déculpabiliser de penser souvent à des nichons, j’ai parcouru l’internet entier à dos de souris et j’ai retrouvé la justification. Mais avant de vous la livrer, quelques explications préliminaires.

On n’a pas de pétrole, mais on a des id()

Et quand on n'a pas d'id, on a des fix !

La fonction id(), présente dans __builtins__, renvoie un entier, représentant l’identifiant interne de n’importe quel objet, quel que soit son type. Concrètement, il s’agit de l’adresse mémoire dans laquelle il est stocké. Jouons un peu avec.

»» a = 1
»» id(a)
19846928
»» b = "plop"
»» id(b)
33984800
»» id(2)
19846916

Si vous faites cela chez vous, vous n’obtiendrez certainement pas les mêmes nombres. Ce qui est important à repérer, c’est que dans ce premier exemple, tous les id sont différents. Au passage, vous remarquerez que même les constantes ont un id. Je ne sais pas exactement comment cela fonctionne en interne, mais je suppose que lorsqu’on écrit le chiffre 2, on oblige le python à stocker cette valeur dans une sorte de monde magique contenant tout ce qui a été utilisé depuis le début de l’exécution, même si certaines de ces valeurs ne sont plus référencées nul part.

Il est parfois intéressant de vérifier que deux id sont égaux ou différents. Mais vous ne pouvez pas faire grand chose de plus. Les caractères de la chaîne “plop” sont, à priori, stockés les uns à la suite des autres, dans une seule zone mémoire (tous les langages de programmation gèrent les strings de cette manière), mais vous ne pouvez pas mettre ce fait en évidence.

»» id(b[0])
19483368
»» id(b[1])
19989736
»» id(b[2])
19989760
»» id(b[0:2])
31983736
»» id(b[1:3])
31983424

Les adresses ne se suivent pas, et ne sont même pas rangées dans l’ordre croissant. Ce n’est pas vraiment exploitable. En revanche, il y a quelque chose de rigolo à constater :

»» c = "pouet"
»» d = "pouet"
»» id(c)
33984768
»» id(d)
33984768
»» id("pouet")
33984768

On a deux variables différentes, contenant la même chose, et elles pointent sur la même adresse mémoire. Mais alors, si je modifie c, ça va aussi modifier d ! Ohlala, Comment le python va faire ? Rassurez-vous, il s’en sort.

»» c += "a"
»» c
'poueta'
»» d
'pouet'
»» id(c)
33855904
»» id(d)
33984768

Le type string est immutable (ndm aussi: “immuable”). Cela signifie que vous ne pouvez rien changer dedans. Tout ce que vous pouvez faire, c’est réaffecter des variables (à une autre string, à n’importe quoi, …). Dans cet exemple, la réaffectation de c l’a fait pointer sur une autre zone de la mémoire. Mais sinon, la variable d n’a pas changé, ni son pointage, ni le contenu de ce qui est pointé. Ouf !!

Je te fais "pouet-pouet", tu me fais "pouet-pouet" ...

Au passage, je rappelle que toutes les fonctions de traitement de chaîne de caractères (replace(), upper(), strip(), …) ne font jamais la modification “sur place”. Elles renvoient la valeur sous forme d’une nouvelle string. Si vous voulez modifier sur place, vous êtes obligés de faire une réaffectation explicite. Quelque chose comme : c = c.upper()

Bidouilleries optimisatoires internes

Je sens que vous redemandez encore du jeu de mots pourri. So, here comze da None !

Le fait qu’un objet soit immutable n’oblige pas le python à créer une seule zone mémoire par valeur. Dans l’exemple précédent, lorsque j’ai affecté les variables c et d à “pouet”, leurs id auraient pu être différentes dès le départ, et les zones mémoires pointées auraient toutes les deux contenues “pouet”. Il se trouve que le python les a mises en commun, par souci d’optimisation. Cependant, je ne suis pas sûr que cette technique soit appliquée par tous les interpréteurs, toutes les versions, et dans tous les contextes. Mais “chez moi, ça marche”.

Cette optimisation est également effectuée pour les valeurs booléennes et les valeurs numériques entières. Mais pas pour les floats, alors qu’à ma connaissance, ils sont eux aussi immutables.

»» id(True)
505281316
»» id(True)
505281316
»» id(False)
505280988
»» id(False)
505280988
»» id(10)
19846820
»» id(10)
19846820
»» id(10.0)
19879104
»» id(10.0)
31338664
# Refaites des id(10.0), et vous aurez encore d'autres valeurs différentes.
# Mais peut-être pas à chaque fois. On ne peut pas le prévoir.

En revanche, l’optimisation de la valeur None est garantie, quel que soit votre python et son contexte. Il n’y a toujours qu’une et une seule instance de None dans une exécution de python. Je n’ai pas trouvé d’annonce officielle concernant cette garantie, mais ça se dit un peu partout sur les forums. (Et si c’est sur internet, c’est forcément vrai).

Jouons un peu avec la singletonitude du None.

»» id(None)
505255972
»» z = None
»» id(z)
505255972
»» zzzz = [None, ] * 30
»» zzzz
[None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None]
»» [ id(elem) for elem in zzzz ]
[505255972, 505255972, 505255972, 505255972, 505255972, 505255972, 505255972,
505255972, 505255972, 505255972, 505255972, 505255972, 505255972, 505255972,
505255972, 505255972, 505255972, 505255972, 505255972, 505255972, 505255972,
505255972, 505255972, 505255972, 505255972, 505255972, 505255972, 505255972,
505255972, 505255972]

Disgression : les mot-clés True et False peuvent être réaffectés. Mais pas le mot-clé None. Mais je m’égare. Revenons à nos agneaux, comme dirait Hannibal Lecter.

noir is noir. (Il n’y a plus d’ayspouuaaaarr).

"Mmmmppff... psshhhh..." (#000000 Vador)

Laissez-moi maintenant vous introduire (schlorrp) l’opérateur is. Il est présent nativement dans le python, et permet de comparer les id de deux objets. A is B renvoie un booléen, le même que celui que renverrait id(A) == id(B).

A quoi cela pourrait-il donc servir ? Eh bien … De même que la fonction id ne sert pas à grand chose, is est, lui aussi, d’une utilité discutable. Mais attendez, ne partez pas tout de suite.

L’opérateur is est vraiment tout simple. Il ne peut pas être surchargé, et fait toujours la même chose quel que soit les opérandes : une comparaison d’id.

L’opérateur == a l’air tout bête, vu comme ça, mais il peut être surchargé, et il effectue une suite d’actions pas forcément triviales. Il appelle la fonction __eq__ si elle existe, sinon il essaie avec la fonction __cmp__, et peut-être encore d’autres choses que je ne détaillerai pas ici, parce que je ne me suis pas renseigné en détail sur le sujet. (Faut que je conserve une partie de mon temps pour le pensage à des nichons, vous comprenez bien).

En règle générale, dès que c’est possible, il vaut mieux utiliser un truc simple plutôt qu’un truc compliqué. Malheureusement, comme on ne maîtrise pas vraiment la façon dont les id sont gérés et optimisés, l’utilisation du is serait trop risquée. Sauf dans un cas bien identifié. Devinez lequel ?

Le None, qui n’a toujours qu’une et une seule id ! Joie ! Noël !

Au fait, pendant qu’on y est, ne pourrait-on pas utiliser is avec les valeurs True et False ? A priori, ces deux valeurs sont gérées de la même manière que le None ?

Certes, mais pour les booléens, on a encore plus simple. Au lieu de tester if A is True: ou if A == True:, on peut directement tester if A:. Pour False, on teste if not A:. Voili voilà.

Le béni oui-oui

Un béni oui-oui !

Pour finir, une petite justification supplémentaire, que je n’ai jamais rencontré dans la vraie vie, mais on va faire comme si.

Alors voilà, vous êtes quelqu’un de bizarre, et vous avez besoin d’une classe qui est égale à tout. Donc vous surchargez la fonction __eq__.

»» class BeniOuiOui:
    def __eq__(self, other):
        # Je suis une classe magique, qui est égale à tout ce qui existe !
        return True
»» beniOuiOui = BeniOuiOui()

Un peu plus loin dans le code, vous avez une variable, récupérée d’on ne sait trop où, et vous voulez savoir si c’est None, ou si c’est une instance de béni-oui-oui. Pour faire cela, vous êtes obligés d’utiliser is, car == vous répondrait systématiquement True.

»» beniOuiOui == 2
True
»» beniOuiOui == "n'importe quoi"
True
»» beniOuiOui == None
True
»» beniOuiOui is None
False

this (is not) une pipe

Ce serait même plutôt le contraire.

Comment fait-on pour vérifier que quelque chose n’est pas None ? La première réponse qui vient à l’esprit, c’est not A is None. Mais on peut aussi utiliser A is not None. C’est chouette, ça ressemble à du langage naturel.

Oh, mais, attendez … Est-ce que cette écriture n’est pas un peu dangereuse ?

»» not None
True

not None renvoie True. Pourquoi pas. C’est ce qui semble le plus logique.

»» 1 is True
False

1 is True est faux. Ca semble correct aussi. La valeur numérique 1 et la valeur booléenne True sont égales, mais ce ne SONT pas les mêmes objets.

Mais alors, si j’écris 1 is not None, le python va transformer le not None en True, ça va faire 1 is True, et ça va renvoyer faux. Ce n’est pas ce que je veux ! La valeur numérique 1, ce n’est pas None. Moi je veux que 1 is not None me renvoie vrai.

Arrggh ! Pleurons mes amis ! Et regardons avec horreur le python se désintégrer tout seul dans une bouffée de logique !

»» 1 is not None
True

Ah tiens non. Hmmm… Attendez voir…

»» 1 is (not None)
False

Qu’est-ce donc que cela ? Figurez-vous que is not est un opérateur. Il compare les id, et renvoie True s’ils sont différents. Je vous laisse confirmer cela en consultant la doc du python, ou en faisant un petit help("is") dans la console.

Voilà, j’espère que ça vous a plu. La prochaine fois, nous essaierons de déceler dans le langage Brainfuck des éléments de la thèse existentialiste selon Jean-Paul Sartre.