Qu'est-ce que la méthode __len__ ?
La méthode spéciale __len__ permet de définir le comportement de la fonction len() sur les objets des classes que vous créez. Imaginez une classe Panier : si vous créez une instance panier_patrick et que vous faites len(panier_patrick), Python appelle panier_patrick.__len__() en arrière-plan.
Reprenons l'exemple de notre glossaire sur __add__, sauf que maintenant Patrick veut savoir combien d'articles il a dans son panier. Et on va lui ajouter un attribut nom pour être sûr que Patrick ne se trompe pas de panier.
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles panier_patrick = Panier("Patrick", ["pommes", "pâtes en forme de dinosaures"]) print(len(panier_patrick)) # TypeError: object of type 'Panier' has no len()
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
L'objet Panier n'a pas de longueur, car Python ne sait pas ce qu'il doit utiliser dans la classe pour déterminer la longueur. Je vous laisse deviner que nous allons utiliser l'attribut articles, ce qui paraît logique ici.
Comment implémenter len ?
Il suffit de définir la méthode __len__ et de choisir ce qu'elle doit prendre en compte :
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles def __len__(self): return len(self.articles) panier_patrick = Panier("Patrick", ["pommes", "pâtes en forme de dinosaures"]) panier_sebastien = Panier("Sebastien", ["café", "jouet licorne", "chocolat"]) panier_vide = Panier("Melanie", []) print(len(panier_patrick)) # 2 print(len(panier_sebastien)) # 3 print(len(panier_vide)) # 0
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
On utilise ici l'attribut articles pour définir la longueur de l'objet, rien d'autre.
Du coup, si vous souhaitez faire des blagues aux collègues, vous pouvez définir un comportement complètement abusé :
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles def __len__(self): return len(self.articles) + 100000000000 panier_patrick = Panier("Patrick", ["pommes", "pâtes en forme de dinosaures"]) panier_sebastien = Panier("Sebastien", ["café", "jouet licorne", "chocolat"]) panier_vide = Panier("Melanie", []) print(len(panier_patrick)) # 100000000002 print(len(panier_sebastien)) # 100000000003 print(len(panier_vide)) # 100000000000
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Tout ça pour vous montrer que c'est vous qui décidez de l'attribut qui sert de référence pour calculer la longueur d'un objet.
Que doit renvoyer len ?
La méthode doit renvoyer un entier positif ou nul, sinon Python vous rappellera à l'ordre :
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles def __len__(self): return 2.5 panier_patrick = Panier("Patrick", ["pommes", "pâtes en forme de dinosaures"]) print(len(panier_patrick)) # TypeError: 'float' object cannot be interpreted as an integer
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles def __len__(self): return -1 panier_patrick = Panier("Patrick", ["pommes", "pâtes en forme de dinosaures"]) print(len(panier_patrick)) # ValueError: __len__() should return >= 0
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Attention
Ces vérifications ne sont pas faites par votre méthode, mais bien par la fonction len(). Rien ne vous empêche d'appeler la méthode directement, et elle vous renverra ce que vous voulez… C'est d'ailleurs pour cette raison que l'on passe toujours par la fonction len().
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles def __len__(self): return -1 panier_patrick = Panier("Patrick", ["pommes", "pâtes en forme de dinosaures"]) print(panier_patrick.__len__()) # -1
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
Pourquoi mon panier vide est-il considéré comme False ?
Une fonctionnalité de Python que vous utilisez certainement déjà : utiliser if sur un objet. Python convertit votre objet en booléen. En fait, sous le capot, Python cherche d'abord la méthode __bool__, et si elle n'existe pas, il se replie sur __len__. Si la longueur est de zéro, l'objet est faux, sinon vrai.
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles def __len__(self): return len(self.articles) panier_patrick = Panier("Patrick", ["pommes", "pâtes en forme de dinosaures"]) panier_vide = Panier("Melanie", []) print(bool(panier_patrick)) # True print(bool(panier_vide)) # False if panier_vide: print("Il y a des articles") else: print("Le panier est vide") # Le panier est vide
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
En définissant simplement __len__, notre Panier se comporte comme une liste : s'il est vide, il est faux.
Mais que se passe-t-il sans __len__ ni __bool__ ?
class Panier: def __init__(self, nom, articles): self.nom = nom self.articles = articles panier_vide = Panier("Lucien", []) print(bool(panier_vide)) # True
Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.
L'objet est toujours vrai.
À noter
Petit fun fact (ou pas), len() ne fonctionne pas sur un générateur : len(x for x in range(10)) lèvera une TypeError. Un générateur produit les valeurs à la demande et ne connaît pas sa longueur à l'avance.