Alex a bien résumé mais, étonnamment, il a été trop succinct.
Tout d'abord, permettez-moi de réitérer les principaux points du post d'Alex :
- L'implémentation par défaut est inutile (il est difficile de penser à une implémentation qui ne le serait pas, mais oui)
-
__repr__
l'objectif est d'être sans ambiguïté
-
__str__
l'objectif est d'être lisible
- Container's
__str__
utilise les "objets contenus". __repr__
L'implémentation par défaut est inutile
C'est surtout une surprise car les valeurs par défaut de Python ont tendance à être assez utiles. Cependant, dans ce cas, le fait d'avoir une valeur par défaut pour le paramètre __repr__
qui agirait comme :
return "%s(%r)" % (self.__class__, self.__dict__)
aurait été trop dangereux (par exemple, trop facile de se retrouver dans une récursion infinie si les objets se référencent les uns les autres). Donc Python s'en sort. Notez qu'il existe un défaut qui est vrai : si __repr__
est défini, et __str__
ne l'est pas, l'objet se comportera comme si __str__=__repr__
.
Cela signifie, en termes simples, que presque chaque objet que vous implémentez doit avoir une fonction __repr__
qui soit utilisable pour comprendre l'objet. Mise en œuvre de __str__
est facultatif : faites-le si vous avez besoin d'une fonctionnalité de "jolie impression" (par exemple, utilisée par un générateur de rapports).
L'objectif de __repr__
doit être sans ambiguïté
Permettez-moi de le dire franchement : je ne crois pas aux débogueurs. Je ne sais pas vraiment comment utiliser un débogueur, et je n'en ai jamais utilisé un sérieusement. De plus, je crois que le grand défaut des débogueurs est leur nature même - la plupart des échecs que je débogue se sont produits il y a très longtemps, dans une galaxie très lointaine. Cela signifie que je crois, avec une ferveur religieuse, à la journalisation. La journalisation est l'élément vital de tout système de serveur décent "fire-and-forget". Python facilite la journalisation : avec peut-être quelques wrappers spécifiques à un projet, tout ce dont vous avez besoin est un fichier de type
log(INFO, "I am in the weird function and a is", a, "and b is", b, "but I got a null C — using default", default_c)
Mais vous devez faire la dernière étape - vous assurer que chaque objet que vous implémentez a une représentation utile, afin que le code comme celui-ci puisse fonctionner. C'est pour cela que l'on parle de "eval" : si vous avez suffisamment d'informations pour que eval(repr(c))==c
cela signifie que vous savez tout ce qu'il y a à savoir sur c
. Si c'est assez facile, au moins de manière floue, faites-le. Sinon, assurez-vous d'avoir suffisamment d'informations sur les points suivants c
de toute façon. J'utilise généralement un format de type eval : "MyClass(this=%r,that=%r)" % (self.this,self.that)
. Cela ne signifie pas que vous pouvez réellement construire MyClass, ou que ce sont les bons arguments du constructeur - mais c'est une forme utile pour exprimer "c'est tout ce que vous devez savoir sur cette instance".
Note : J'ai utilisé %r
ci-dessus, pas %s
. Vous voulez toujours utiliser repr()
[ou %r
caractère de formatage, de manière équivalente] à l'intérieur __repr__
ou vous allez à l'encontre de l'objectif de la répression. Vous voulez être capable de différencier MyClass(3)
et MyClass("3")
.
L'objectif de __str__
est d'être lisible
Plus précisément, il n'est pas destiné à être sans ambiguïté - remarquez que str(3)==str("3")
. De la même manière, si vous implémentez une abstraction IP, le fait que la chaîne de caractères ressemble à 192.168.1.1 est tout à fait acceptable. Lorsque vous implémentez une abstraction de date/heure, la chaîne peut être "2010/4/12 15:35:22", etc. L'objectif est de la représenter de manière à ce qu'un utilisateur, et non un programmeur, veuille la lire. Supprimez les chiffres inutiles, faites semblant d'être une autre classe - tant que cela permet la lisibilité, c'est une amélioration.
Container's __str__
utilise les "objets contenus". __repr__
Cela semble surprenant, n'est-ce pas ? C'est un peu, mais à quel point serait-il lisible
[moshe is, 3, hello
world, this is a list, oh I don't know, containing just 4 elements]
être ? Pas vraiment. Plus précisément, les chaînes d'un conteneur trouveraient qu'il est beaucoup trop facile de perturber sa représentation des chaînes. Face à l'ambiguïté, rappelez-vous que Python résiste à la tentation de deviner. Si vous voulez le comportement ci-dessus lorsque vous imprimez une liste, il suffit de
print "["+", ".join(l)+"]"
(vous pouvez probablement aussi trouver une solution pour les dictionnaires.
Résumé
Mettre en œuvre __repr__
pour toute classe que vous implémentez. Cela devrait être une seconde nature. Implémenter __str__
si vous pensez qu'il serait utile de disposer d'une version de chaîne de caractères qui privilégie la lisibilité au détriment de l'ambiguïté.