La magie des booléens en Python


Dans un langage fortement typé comme Python, on ne peut pas additionner des choux et des carottes, comme disait madame Germaine, ma prof de mate.

Photo d'une pinup déguisée en prof

J'ai toujours eu des soucis d'intégration en cours de maths. Mon attention était discontinue et je ne me sentais pas dans mon domaine.

Par exemple on ne peut pas faire ça :

>>> 1 + "1"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Nonnnnnnnn !

Ni ça :

>>> [1] + (1,)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "tuple") to list

Pas biennnnnnnnnnnnnn !

Et pas ça :

>>> True + 1
2

OUATE ZE PHOQUE ?

Les booléens sont des entiers déguisés

Pendant longtemps, Python n’a pas eu de type bool, et on utilisait, comme en C, 0 pour faux, et 1 pour vrai. Ça marchait pas mal, mais dans un souci de rentre le langage plus propre, on a voulu créer une type spécialisé, et ainsi :

>>> type(True)
<class 'bool'>
>>> type(1)
<class 'int'>

Mais il faut avouer que pouvoir interchanger True pour 1 et False pour 0 et vice versouille, c’était achement pratique dans une discipline ou la manipulation des chiffres à des fins logiques est un peu la base du métier. Du coup il a été décidé que les booléen seraient des travs, juste une surcouche au dessus des entiers, et qu’on pourrait les utiliser en lieu et place de ceux-ci. Une exception étonnante vu la rigueur du langage sur la gestion des types.

Du coup :

>>> True == 1
True
>>> False == 0
True
>>> True + 1
2
>>> False - 1
-1

Par contre :

>>> 1 is 1
True
>>> True is True
True
>>> True is 1
False

Et qui dit exception, dit hack

Donc voici quelques moyens de détourner l’utilisation des booléens pour écrire du Perlthon…

On peut utiliser un bool pour de l’indexing ou du slicing :

>>> ('Valeur 1', 'Valeur 2')[True]
'Valeur 2'
>>> list(range(10)[True:False-1:2])
[1, 3, 5, 7]

On peut utiliser un bool pour multiplier, et l’opération multiplier marche sur les strings et les listes…

>>> "J'aime les chat" + "s" * est_pluriel
"J'aime les chat"
>>> est_pluriel = True
>>> "J'aime les chat" + "s" * est_pluriel
"J'aime les chats"
>>> def process(lst, cancel=False):
...     lst = list(lst) * (not cancel)
...     for i in lst:
...         print(i)
... 
>>> process(range(3))
0
1
2
>>> process(range(3), True)

Ou même pire, saviez-vous que la déclaration d’héritage et les clauses d’exception pouvaient inclure une expression ? En rajoutant de l’unpacking, on obtient un truc bien marrant :

>>> class Parent:
...     foo = "bar"
... 
>>> inherit = False
>>> class Enfant(*[Parent] * inherit):
...     pass
... 
>>> Enfant().foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Enfant' object has no attribute 'foo'
>>> inherit = True
>>> class Enfant(*[Parent] * inherit):
...     pass
... 
>>> Enfant().foo
'bar'
>>> catch = True
>>> try:
...     42 / 0
... except (ZeroDivisionError,) * catch:
...     print("Nope")
Nope
>>> catch = False
>>> try:
...     42 / 0
... except (ZeroDivisionError,) * catch:
...     print("Nope")
Traceback (most recent call last):
  File "<ipython-input-12-c01f7e27284d>", line 2, in <module>
    42 / 0
ZeroDivisionError: division by zero

Évidelement ça marche avec n’importe quels entiers, mais où serait le fun ?

Et on peut utiliser un bool pour incrémenter une valeur.

>>> increment = True
>>> value = 0
>>> for x in range(10):
...     value += increment
... 
>>> value
10
>>> increment = False
>>> for x in range(10):
...     value += increment
... 
>>> value
10

Voilà de quoi bien vous marrer au boulot lors de votre prochaine code review !

9 thoughts on “La magie des booléens en Python

  • kontre

    Du Perlthon ! Ça fait froid dans le dos.

    Le seul truc que je connaisse qui pouvait être utile là dedans c’est ('Valeur 1', 'Valeur 2')[True] pour l’opérateur ternaire.

    Avant le truc if condition else autre_truc, on utilisait souvent condition and truc or autre_truc. Seulement ça capote si truc s’évalue à False :

    get_empty_string = False
    "" if get_empty_string else "non_empty"  # "non_empty"
    get_empty_string and "" or "non_empty"  # "non_empty"
    ("non_empty", "")[get_empty_string]  # "non_empty"
    get_empty_string = True
    "" if get_empty_string else "non_empty"  # ''
    get_empty_string and "" or "non_empty"  # "non_empty"  WTF!!!
    ("non_empty", "")[get_empty_string]  # ''
  • Yohann

    n’importe quoi les booléens en python, tout le monde sait qu’un booléen digne de ce nom doit pouvoir prendre au minimum 3 valeurs: True, False et ‘File_not_Found’

  • groug

    Ah ouais, très tordu !
    C’est chouette de savoir qu’il y a moyen de faire du code très compliqué avec un langage très simple. Y a pas de raison qu’on soit obligé de faire du code clair

    class Enfant(*[Parent] * inherit):

    Ca ne marche pas, chez moi (invalid syntax, Python 2.7)
    Testé avec Python 3, ça marche.

    Alors, future imports, ou on utilise Python 3 chez S&M ?

  • groug

    Hum, ce serait ça (issu du What’s New in Python 3.0) ?
    J’ai l’impression que ce n’est possible qu’avec Python 3.

    PEP 3132: Extended Iterable Unpacking. You can now write things like a, b, *rest = some_sequence. And even *rest, a = stuff. The rest object is always a (possibly empty) list; the right-hand side may be any iterable. Example:

    (a, *rest, b) = range(5)
    This sets a to 0, b to 4, and rest to [1, 2, 3].

  • Sam Post author

    @Yohann : seuls ceux qui lisent the DWTF peuvent comprendre cette vanne.

  • Salim

    Script Nombres Premiers.

    Je débute sur Python et je me heurte à la syntaxe qui n’est pas intuitive parfois.

    J’essaye de comprendre un petit programme qui donne les nombres premiers dans une tranche (2, n) par exemple.

    le programme est le suivant, il fonctionne évidement :

    def primes_method(n):

    out = list()

    sieve = [True] * (n+1)

    for p in range(2, n+1):

    if (sieve[p]):

    out.append(p)

    for i in range(p, n+1, p):

    sieve[i] = False

    return out

    c’est surtout la ligne : sieve = [True] * (n+1) que je ne comprends pas. J’arrive pas à comprendre le rôle de [True] ici…

    Pouvez vous- m’expliquer le rôle de cette étrange définition de variable “sieve”!?

    Merci.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.