Qu'est-ce que la méthode __bool__ ?
Tout objet possède une valeur dite de vérité en Python. En fait, dès qu'on le place dans un if, un while ou encore un appel à bool(), il sera considéré comme vrai ou faux. __bool__ est la méthode spéciale qui permet à vos propres classes de déterminer cette valeur.
class Panier: def __init__(self, articles): self.articles = articles def __bool__(self): return len(self.articles) > 0 panier_patrick = Panier([]) panier_sebastien = Panier(["clavier", "souris"]) print(bool(panier_patrick)) # False print(bool(panier_sebastien)) # True if panier_sebastien: print("Le panier contient des articles") # Le panier contient des articles
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Vous n'appelez jamais __bool__ directement : Python s'en charge pour vous. Voici quelques cas :
-
if objet/while objet -
bool(objet) -
objet and autre_objet -
"patrick" if objet else "sebastien" -
any(elements)/all(elements)
Comment Python détermine-t-il si un objet est vrai ou faux ?
Python suit un ordre précis pour déterminer si un objet est vrai ou faux :
-
Si la classe définit
__bool__, Python appelle cette méthode -
Sinon, si la classe possède la méthode
__len__, c'est elle qui sera utilisée -
Si aucune des méthodes n'est implémentée, l'objet sera toujours vrai
class Panier: def __init__(self, articles): self.articles = articles class PanierAvecLen: def __init__(self, articles): self.articles = articles def __len__(self): return len(self.articles) class PanierAvecBool: def __init__(self, articles): self.articles = articles def __bool__(self): return bool(self.articles) print(bool(Panier([]))) # True print(bool(PanierAvecLen([]))) # False print(bool(PanierAvecBool([]))) # False
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Comment définir __bool__ dans une classe ?
La méthode ne prend que self et renvoie un booléen. Si votre classe contient une collection, le comportement le plus banal est de laisser la collection répondre :
class Panier: def __init__(self, articles): self.articles = articles def __bool__(self): return bool(self.articles) print(bool(Panier([]))) # False print(bool(Panier(["clavier"]))) # True
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Maintenant, voyons un autre exemple. En effet, on ne va pas se limiter à « est-ce que c'est vide ? ». Prenons un compte client : j'ai envie que True veuille dire « le client peut passer commande ».
class CompteClient: def __init__(self, nom, actif, solde): self.nom = nom self.actif = actif self.solde = solde def __bool__(self): # Le compte est exploitable s'il est actif ET approvisionne return self.actif and self.solde > 0 comptes = [ CompteClient("Patrick", True, 120.0), CompteClient("Sebastien", True, 0.0), CompteClient("Elodie", False, 80.0), ] for compte in comptes: if compte: print(f"{compte.nom} : commande possible") else: print(f"{compte.nom} : commande bloquee") # Patrick : commande possible # Sebastien : commande bloquee # Elodie : commande bloquee
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
À noter
Faites en sorte que la méthode ne produise pas d'effet de bord.
Quelle différence entre __bool__ et __len__ ?
__len__ suffit la plupart du temps quand notre objet est une collection. __bool__ devient surtout intéressant quand on tombe sur un cas comme l'exemple du dessus, c'est-à-dire quand vrai ne veut pas dire non vide. Un autre cas tout aussi intéressant, c'est quand compter est coûteux.
Prenons un autre exemple : un journal d'erreurs dans lequel on veut savoir s'il y a au moins une erreur.
JOURNAL = [ "INFO connexion de Patrick", "ERREUR echec de paiement", "INFO connexion de Sebastien", "ERREUR timeout base de donnees", ] class Recherche: def __init__(self, lignes, terme): self.lignes = lignes self.terme = terme def _contient(self, ligne): print(f"Analyse de : {ligne}") return self.terme in ligne def __bool__(self): # any() s'arrete des la premiere correspondance return any(self._contient(ligne) for ligne in self.lignes) recherche = Recherche(JOURNAL, "ERREUR") if recherche: print("Au moins une erreur dans le journal") # Analyse de : INFO connexion de Patrick # Analyse de : ERREUR echec de paiement # Au moins une erreur dans le journal
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Deux lignes analysées, pas besoin de tout parcourir.
Que se passe-t-il si __bool__ ne renvoie pas de booléen ?
__bool__ exige True ou False :
class Panier: def __init__(self, articles): self.articles = articles def __bool__(self): return len(self.articles) bool(Panier(["clavier"])) # TypeError: __bool__ should return bool, returned int
Un piège un peu vicieux avec and : l'opérateur ne renvoie pas forcément True ou False mais l'une des deux valeurs qu'on lui donne. Prenons un exemple :
class CompteClient: def __init__(self, nom, actif, solde): self.nom = nom self.actif = actif self.solde = solde def __bool__(self): return self.actif and self.solde > 0 # actif vaut 0 (un entier) et non False bool(CompteClient("Patrick", 0, 120.0)) # TypeError: __bool__ should return bool, returned int
actif vaut 0 au lieu de False, __bool__ renvoie donc un entier, ce qui explique l'erreur. Pour régler le problème, on peut utiliser la fonction bool() : bool(self.actif and self.solde > 0).
Ne pas confondre faux et absent
Un objet peut être évalué comme faux mais peut exister 🤯. Je vous laisse méditer… Ça sera plus simple avec un exemple :
class Panier: def __init__(self, articles): self.articles = articles def __bool__(self): return bool(self.articles) def resumer(panier): if not panier: return "Aucun panier fourni" return f"{len(panier.articles)} article(s) dans le panier" print(resumer(None)) # Aucun panier fourni print(resumer(Panier([]))) # Aucun panier fourni <- le panier existe pourtant
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Un panier vide existe bien... même s'il est vide ! L'objet est bien là. Donc pour tester si un objet est bien présent, vérifiez qu'il n'est pas None :
def resumer_corrige(panier): if panier is None: return "Aucun panier fourni" return f"{len(panier.articles)} article(s) dans le panier" print(resumer_corrige(None)) # Aucun panier fourni print(resumer_corrige(Panier([]))) # 0 article(s) dans le panier