web-dev-qa-db-fra.com

Différence entre __str__ et __repr__?

Quelle est la différence entre __str__ et __repr__ en Python?

2472
Casebash

Alex résume bien mais, étonnamment, était trop succinct.

Tout d’abord, permettez-moi de répéter les points principaux de post d’Alex :

  • L’implémentation par défaut est inutile (il est difficile d’en penser une qui ne le serait pas, mais oui)
  • __repr__ but est d'être sans ambiguïté
  • __str__ objectif doit être lisible
  • __str__ du conteneur utilise les __repr__ des objets contenus

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, ayant un défaut pour __repr__ qui agirait comme:

return "%s(%r)" % (self.__class__, self.__dict__)

aurait été trop dangereux (par exemple, il est trop facile d'entrer dans une récursion infinie si les objets se référent). Donc, Python s'est échappé. Notez qu'il existe une valeur par défaut qui est true: si __repr__ est défini et si __str__ ne l’est pas, l’objet se comportera comme si __str__=__repr__.

Cela signifie, en termes simples: presque chaque objet que vous implémentez doit avoir un __repr__ fonctionnel, utilisable pour comprendre l’objet. L'implémentation de __str__ est facultative: faites-le si vous avez besoin d'une fonctionnalité de "jolie impression" (utilisée par exemple par un générateur de rapport).

L'objectif de __repr__ est d'être sans ambiguïté

Laissez-moi sortir et le dire - je ne crois pas aux débogueurs. Je ne sais pas vraiment comment utiliser un débogueur, et je n’en ai jamais utilisé sérieusement. De plus, je pense que le gros défaut des débogueurs est leur nature fondamentale - la plupart des échecs que je débogue se sont produits il y a très longtemps, dans une galaxie très éloignée. Cela signifie que je crois, avec une ferveur religieuse, à l'exploitation forestière. La journalisation est la pierre angulaire de tout système serveur décent "fire-and-oublie". Python facilite la journalisation: avec peut-être des wrappers spécifiques à un projet, tout ce dont vous avez besoin est une

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 - assurez-vous que chaque objet que vous implémentez a un repr utile, afin que le code comme ça puisse fonctionner. C’est la raison pour laquelle "eval" s’affiche: si vous avez assez d’informations pour que eval(repr(c))==c, cela signifie que vous savez tout ce que vous devez savoir sur c. Si cela est assez facile, au moins de manière floue, faites-le. Sinon, assurez-vous de disposer de suffisamment d'informations sur c. J'utilise habituellement un format semblable à eval: "MyClass(this=%r,that=%r)" % (self.this,self.that). Cela ne signifie pas que vous pouvez réellement construire MyClass, ni que ce sont les bons arguments de constructeur, mais c'est une forme utile pour exprimer "c'est tout ce que vous devez savoir sur cette instance".

Remarque: J'ai utilisé %r ci-dessus, pas %s. Vous voulez toujours utiliser repr() [ou %r, de manière équivalente] dans l’implémentation de __repr__, sinon vous éliminez l’objectif de repr. Vous voulez pouvoir différencier MyClass(3) et MyClass("3").

L'objectif de __str__ doit être lisible

Plus précisément, il n'est pas destiné à être sans ambiguïté - notez que str(3)==str("3"). De même, si vous implémentez une abstraction IP, il suffit de donner à str la forme 192.168.1.1. Lors de l'implémentation d'une abstraction de date/heure, str peut être "2010/4/12 15:35:22", etc. Le but est de la représenter de manière à ce qu'un utilisateur, et non un programmeur, veuille la lire. Coupez les chiffres inutiles, faites semblant d'être une autre classe - tant que la lisibilité sera prise en charge, c'est une amélioration.

Le __str__ du conteneur utilise le __repr__ des objets contenus

Cela semble surprenant, n'est-ce pas? C'est un peu, mais comment lisible serait

[moshe is, 3, hello
world, this is a list, oh I don't know, containing just 4 elements]

être? Pas très. Plus précisément, les chaînes d'un conteneur auraient beaucoup trop de mal à perturber sa représentation. 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,

print "[" + ", ".join(l) + "]"

(vous pouvez probablement aussi savoir quoi faire avec les dictionnaires.

Résumé

Implémentez __repr__ pour toute classe que vous implémentez. Cela devrait être une seconde nature. Implémentez __str__ si vous pensez qu'il serait utile de disposer d'une version de chaîne erronée du côté de la lisibilité.

2518
moshez

Ma règle générale: __repr__ est destiné aux développeurs, __str__ est destiné aux clients.

461
Ned Batchelder

Sauf si vous agissez spécifiquement pour vous assurer du contraire, la plupart des classes n'offrent pas de résultats utiles pour:

>>> class Sic(object): pass
... 
>>> print str(Sic())
<__main__.Sic object at 0x8b7d0>
>>> print repr(Sic())
<__main__.Sic object at 0x8b7d0>
>>> 

Comme vous le voyez, aucune différence, ni aucune information au-delà de la classe et de la variable id de l'objet. Si vous ne remplacez que l'un des deux ...:

>>> class Sic(object): 
...   def __repr__(object): return 'foo'
... 
>>> print str(Sic())
foo
>>> print repr(Sic())
foo
>>> class Sic(object):
...   def __str__(object): return 'foo'
... 
>>> print str(Sic())
foo
>>> print repr(Sic())
<__main__.Sic object at 0x2617f0>
>>> 

comme vous le voyez, si vous remplacez __repr__, c'est également utilisé pour __str__, mais pas l'inverse.

Autres informations essentielles à connaître: __str__ sur un conteneur intégré utilise le __repr__, PAS le __str__, pour les éléments qu'il contient. Et, malgré les mots sur le sujet trouvés dans la documentation typique, presque personne ne dérange que le __repr__ des objets soit une chaîne que eval peut utiliser pour construire un objet égal (c'est trop difficile, ET je ne sais pas la manière dont le module concerné a été réellement importé le rend totalement impossible).

Donc, mon conseil: cherchez à rendre __str__ lisible par tout un chacun, et __repr__ aussi sans ambiguïté que possible, même si cela interfère avec l'objectif inatteignable de rendre acceptable la valeur de __repr__. en entrée de __eval__!

371
Alex Martelli

En bref, le but de __repr__ est d'être sans ambiguïté et __str__ doit être lisible.

Voici un bon exemple:

>>> import datetime
>>> today = datetime.datetime.now()
>>> str(today)
'2012-03-14 09:21:58.130922'
>>> repr(today)
'datetime.datetime(2012, 3, 14, 9, 21, 58, 130922)'

Lisez cette documentation pour repr:

repr(object)

Renvoie une chaîne contenant une représentation imprimable d'un objet. Il s'agit de la même valeur générée par les conversions (devis inversés). Il est parfois utile de pouvoir accéder à cette opération en tant que fonction ordinaire. Pour de nombreux types, cette fonction tente de renvoyer une chaîne qui donnerait un objet avec la même valeur si elle est transmise à eval(). Sinon, la représentation est une chaîne entourée de crochets angulaires contenant le nom du type de l'objet avec des informations comprenant souvent le nom et l'adresse de l'objet. Une classe peut contrôler ce que cette fonction retourne pour ses instances en définissant une méthode __repr__().

Voici la documentation pour str:

str(object='')

Renvoie une chaîne contenant une représentation joliment imprimable d'un objet. Pour les chaînes, cela retourne la chaîne elle-même. La différence avec repr(object) est que str(object) ne tente pas toujours de renvoyer une chaîne acceptable pour eval(); son but est de retourner une chaîne imprimable. Si aucun argument n'est fourni, retourne la chaîne vide, ''.

137
bitoffdev

Quelle est la différence entre __str__ et __repr__ en Python?

__str__ (lu comme "chaîne dunder (double soulignement)") et __repr__ (lu comme "dunder-repper" (pour "representation")) sont deux méthodes spéciales qui retournent des chaînes basées sur l'état de l'objet.

__repr__ fournit un comportement de sauvegarde si __str__ est manquant.

Donc, il faut d’abord écrire un __repr__ qui vous permet de restituer un objet équivalent à partir de la chaîne qu’il renvoie, par exemple. en utilisant eval ou en le saisissant caractère par caractère dans un shell Python.

À tout moment, on peut écrire un __str__ pour une représentation sous forme de chaîne lisible par l'utilisateur de l'instance, quand on le juge nécessaire.

__str__

Si vous imprimez un objet ou le transmettez à format, str.format ou str, alors si une méthode __str__ est définie, cette méthode sera appelée, sinon, __repr__ sera utilisé.

__repr__

La méthode __repr__ est appelée par la fonction intégrée repr et correspond à l'écho sur votre shell python lorsqu'il évalue une expression renvoyant un objet.

Puisqu'il fournit une sauvegarde pour __str__, si vous ne pouvez en écrire qu'une, commencez par __repr__

Voici l'aide intégrée sur repr:

repr(...)
    repr(object) -> string

    Return the canonical string representation of the object.
    For most object types, eval(repr(object)) == object.

Autrement dit, pour la plupart des objets, si vous saisissez ce qui est imprimé par repr, vous devriez pouvoir créer un objet équivalent. Mais ce n'est pas l'implémentation par défaut.

Implémentation par défaut de __repr__

L'objet par défaut __repr__ est ( C Python source ) quelque chose comme:

def __repr__(self):
    return '<{0}.{1} object at {2}>'.format(
      self.__module__, type(self).__name__, hex(id(self)))

Cela signifie que par défaut, vous imprimerez le module d'où provient l'objet, le nom de la classe et la représentation hexadécimale de son emplacement en mémoire - par exemple:

<__main__.Foo object at 0x7f80665abdd0>

Cette information n’est pas très utile, mais il n’ya aucun moyen de déterminer comment on pourrait créer avec précision une représentation canonique d’un événement donné, et c’est mieux que rien, du moins nous dire comment nous pourrions l’identifier de manière unique en mémoire.

Comment __repr__ peut-il être utile?

Voyons à quel point cela peut être utile, en utilisant les objets Python Shell et datetime. Nous devons d’abord importer le module datetime:

import datetime

Si nous appelons datetime.now dans le shell, nous verrons tout ce dont nous avons besoin pour recréer un objet datetime équivalent. Ceci est créé par le date/heure __repr__:

>>> datetime.datetime.now()
datetime.datetime(2015, 1, 24, 20, 5, 36, 491180)

Si nous imprimons un objet date/heure, nous voyons un format lisible par Nice (en fait, ISO). Ceci est implémenté par __str__:

>>> print(datetime.datetime.now())
2015-01-24 20:05:44.977951

Il est simple de recréer l’objet perdu car nous ne l’avons pas affectée à une variable en copiant-collant à partir de la sortie __repr__, puis en l’imprimant, et nous l’avons dans le même résultat lisible par l’homme l'autre objet:

>>> the_past = datetime.datetime(2015, 1, 24, 20, 5, 36, 491180)
>>> print(the_past)
2015-01-24 20:05:36.491180

Comment est-ce que je les implémente?

Au fur et à mesure que vous développez, vous souhaiterez pouvoir reproduire des objets dans le même état, si possible. C’est le cas, par exemple, de la façon dont l’objet datetime définit __repr__ ( source Python ). C'est assez complexe, à cause de tous les attributs nécessaires pour reproduire un tel objet:

def __repr__(self):
    """Convert to formal string, for repr()."""
    L = [self._year, self._month, self._day,  # These are never zero
         self._hour, self._minute, self._second, self._microsecond]
    if L[-1] == 0:
        del L[-1]
    if L[-1] == 0:
        del L[-1]
    s = "%s.%s(%s)" % (self.__class__.__module__,
                       self.__class__.__qualname__,
                       ", ".join(map(str, L)))
    if self._tzinfo is not None:
        assert s[-1:] == ")"
        s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
    if self._fold:
        assert s[-1:] == ")"
        s = s[:-1] + ", fold=1)"
    return s

Si vous voulez que votre objet ait une représentation plus lisible par l'homme, vous pouvez implémenter __str__ next. Voici comment l'objet datetime ( source Python ) implémente __str__, ce qu'il fait facilement car il dispose déjà d'une fonction pour l'afficher au format ISO:

def __str__(self):
    "Convert to string, for str()."
    return self.isoformat(sep=' ')

Régler __repr__ = __str__?

Ceci est une critique d'une autre réponse ici qui suggère de définir __repr__ = __str__.

La définition de __repr__ = __str__ est idiote - __repr__ est un repli pour __str__ et un __repr__, écrit à l'usage des développeurs pour le débogage, doit être écrit avant d'écrire un __str__ .

Vous n'avez besoin d'un __str__ que lorsque vous avez besoin d'une représentation textuelle de l'objet.

Conclusion

Définissez __repr__ pour les objets que vous écrivez afin que vous et les autres développeurs disposiez d'un exemple reproductible lorsque vous l'utilisez au cours de votre développement. Définissez __str__ lorsque vous avez besoin d’une représentation sous forme de chaîne lisible par l’homme.

102
Aaron Hall

Outre toutes les réponses données, je voudrais ajouter quelques points: -

1) __repr__() est appelé lorsque vous écrivez simplement le nom de l'objet sur la console interactive python et appuyez sur Entrée.

2) __str__() est appelé lorsque vous utilisez un objet avec une instruction print.

3) Dans le cas où __str__ est manquant, alors l'impression et toute fonction utilisant str() invoquera __repr__() de l'objet.

4) __str__() des conteneurs, une fois appelé, exécutera la méthode __repr__() de ses éléments contenus.

5) str() appelée dans __str__() pourrait potentiellement être récursive sans cas de base et erreur sur la profondeur de récursivité maximale.

6) __repr__() peut appeler repr() qui tentera d'éviter automatiquement la récursion infinie en remplaçant un objet déjà représenté par ....

25

Sur la page 358 du livre Scripting Python pour la science informatique de Hans Petter Langtangen, il est clairement indiqué que

  • Le __repr__ vise une représentation sous forme de chaîne complète de l'objet;
  • Le __str__ doit renvoyer une chaîne de Nice pour l'impression.

Donc, je préfère les comprendre comme

  • repr = reproduit
  • str = chaîne (représentation)

du point de vue de l'utilisateur bien que ce soit un malentendu que j'ai fait lors de l'apprentissage de python.

Un petit mais bon exemple est également donné sur la même page comme suit:

Exemple

In [38]: str('s')
Out[38]: 's'

In [39]: repr('s')
Out[39]: "'s'"

In [40]: eval(str('s'))
Traceback (most recent call last):

  File "<ipython-input-40-abd46c0c43e7>", line 1, in <module>
    eval(str('s'))

  File "<string>", line 1, in <module>

NameError: name 's' is not defined


In [41]: eval(repr('s'))
Out[41]: 's'
21
Yong Yang

En toute honnêteté, eval(repr(obj)) n'est jamais utilisé. Si vous l'utilisez, arrêtez-vous, car eval est dangereux et les chaînes sont un moyen très inefficace de sérialiser vos objets (utilisez plutôt pickle).

Par conséquent, je recommanderais de régler __repr__ = __str__. La raison en est que str(list) appelle repr sur les éléments (j'estime qu'il s'agit de l'un des plus gros défauts de conception de Python qui n'a pas été résolu par Python 3) Un repr réel ne sera probablement pas très utile en tant que résultat de print [your, objects].

D'après mon expérience, le cas d'utilisation le plus utile de la fonction repr consiste à insérer une chaîne dans une autre chaîne (à l'aide du formatage de chaîne). De cette façon, vous n'avez pas à vous soucier d'échapper à des citations ou quoi que ce soit. Mais notez qu'il n'y a pas de eval qui se passe ici.

12
asmeurer

Pour le dire simplement:

__str__ est utilisé pour afficher une représentation sous forme de chaîne de votre objet à lire facilement par d'autres.

__repr__ est utilisé pour afficher une représentation sous forme de chaîne de l'objet the.

Supposons que je souhaite créer une classe Fraction dans laquelle la représentation sous forme de chaîne d'une fraction est '(1/2)' et l'objet (classe de fraction) doit être représenté par 'Fraction (1,2)'

Nous pouvons donc créer une classe de fractions simple:

class Fraction:
    def __init__(self, num, den):
        self.__num = num
        self.__den = den

    def __str__(self):
        return '(' + str(self.__num) + '/' + str(self.__den) + ')'

    def __repr__(self):
        return 'Fraction (' + str(self.__num) + ',' + str(self.__den) + ')'



f = Fraction(1,2)
print('I want to represent the Fraction STRING as ' + str(f)) # (1/2)
print('I want to represent the Fraction OBJECT as ', repr(f)) # Fraction (1,2)
11
Zer0

De n (Un non officiel) Python Wiki de référence (copie d'archive) par effbot:

__str__ " calcule la représentation sous forme de chaîne" informelle "d'un objet. Elle diffère de __repr__ en ce qu'elle ne doit pas nécessairement être valide Python expression: une représentation plus pratique ou plus concise peut être utilisée à la place. "

10
Casebash

Un aspect qui manque dans d'autres réponses. Il est vrai qu'en général, le schéma est le suivant:

  • But de __str__: lisible par l'homme
  • But de __repr__: sans ambiguïté, éventuellement lisible par machine via eval

Malheureusement, cette différenciation est erronée, car Python REPL ainsi que IPython utilisent __repr__ pour imprimer des objets dans une console REPL (voir les questions connexes à ce sujet). Python et IPython ). Ainsi, les projets destinés au travail en console interactive (par exemple, Numpy ou Pandas) ont commencé à ignorer les règles ci-dessus et à fournir une implémentation __repr__ lisible par l'homme.

7
bluenote10

str - Crée un nouvel objet chaîne à partir de l'objet donné.

repr - Retourne la représentation canonique de l'objet.

Les différences:

str ():

  • rend l'objet lisible
  • génère une sortie pour l'utilisateur final

repr ():

  • a besoin d'un code qui reproduit l'objet
  • génère une sortie pour le développeur
6
Inconnu

Tellement plus clair de blog

str est comme toString. créé afin que vous puissiez imprimer les données repr est comme sérialiser, ou pickle. Comment je recrée-t-il cet objet si j'ai besoin de le faire en utilisant eval ()

>>> import datetime
>>> now = datetime.datetime.now() 
>>> str(now)
'2015-04-04 20:51:31.766862'
>>> repr(now)
'datetime.datetime(2015, 4, 4, 20, 51, 31, 766862)'
>>mydate = eval(repr(now))
5
Mickey Perlstein

Extrait du livre Fluent Python:

Une exigence de base pour un objet Python est de fournir ses propres représentations sous forme de chaîne, l'une utilisée pour le débogage et la journalisation, l'autre pour la présentation aux utilisateurs finaux. C'est pourquoi le
Des méthodes spéciales __repr__ et __str__ existent dans le modèle de données.

5
Ijaz Ahmad Khan

D'excellentes réponses couvrent déjà la différence entre __str__ et __repr__, ce qui pour moi revient à dire que la première est lisible, même par un utilisateur final, et que ce dernier est aussi utile que possible aux développeurs. Cela dit, j’estime que l’implémentation par défaut de __repr__ échoue souvent à atteindre cet objectif car elle omet des informations utiles aux développeurs.

Pour cette raison, si j’ai un assez simple __str__, j’essaie généralement d’obtenir le meilleur des deux mondes avec quelque chose comme:

def __repr__(self):
    return '{0} ({1})'.format(object.__repr__(self), str(self))
4
orome
>>> print(decimal.Decimal(23) / decimal.Decimal("1.05"))
21.90476190476190476190476190
>>> decimal.Decimal(23) / decimal.Decimal("1.05")
Decimal('21.90476190476190476190476190')

Lorsque print() est appelé sur le résultat de decimal.Decimal(23) / decimal.Decimal("1.05"), le numéro brut est imprimé. cette sortie est en sous forme de chaîne qui peut être obtenue avec __str__(). Si nous saisissons simplement l'expression, nous obtenons une sortie decimal.Decimal - cette sortie est sous la forme forme de représentation qui peut être obtenue avec __repr__(). Tous les objets Python ont deux formes de sortie. La forme de chaîne est conçue pour être lisible par l'homme. La forme de représentation est conçue pour produire une sortie qui, si elle était transmise à un interprète Python, reproduirait (dans la mesure du possible) l'objet représenté.

3
BattleDrum

Une chose importante à garder à l'esprit est que le __str__ du conteneur utilise les objets contenus '__repr__.

>>> from datetime import datetime
>>> from decimal import Decimal
>>> print (Decimal('52'), datetime.now())
(Decimal('52'), datetime.datetime(2015, 11, 16, 10, 51, 26, 185000))
>>> str((Decimal('52'), datetime.now()))
"(Decimal('52'), datetime.datetime(2015, 11, 16, 10, 52, 22, 176000))"

Python privilégie la lisibilité sans ambiguïté, l'appel __str__ d'un Tuple appelle les objets contenus '__repr__, le "formel" représentation d'un objet. Bien que la représentation formelle soit plus difficile à lire qu’une représentation informelle, elle est sans ambiguïté et plus robuste contre les bugs.

3
zangw

En un mot:

class Demo:
  def __repr__(self):
    return 'repr'
  def __str__(self):
    return 'str'

demo = Demo()
print(demo) # use __str__, output 'str' to stdout

s = str(demo) # __str__ is used, return 'str'
r = repr(demo) # __repr__ is used, return 'repr'

import logging
logger = logging.getLogger(logging.INFO)
logger.info(demo) # use __str__, output 'str' to stdout

from pprint import pprint, pformat
pprint(demo) # use __repr__, output 'repr' to stdout
result = pformat(demo) # use __repr__, result is string which value is 'str'
3
ShadowWalker

Comprenez __str__ et __repr__ de manière intuitive et distincte.

__str__ retourne le corps déguisé en chaîne d'un objet donné pour qu'il soit lisible par les yeux
__repr__ renvoie le corps de chair réel d'un objet donné (se retourne lui-même) afin qu'il soit identifié sans ambiguïté.

Voir dans un exemple

In [30]: str(datetime.datetime.now())
Out[30]: '2017-12-07 15:41:14.002752'
Disguised in string form

Quant à __repr__

In [32]: datetime.datetime.now()
Out[32]: datetime.datetime(2017, 12, 7, 15, 43, 27, 297769)
Presence in real body which allows to be manipulated directly.

Nous pouvons effectuer des opérations arithmétiques sur les résultats de __repr__ facilement.

In [33]: datetime.datetime.now()
Out[33]: datetime.datetime(2017, 12, 7, 15, 47, 9, 741521)
In [34]: datetime.datetime(2017, 12, 7, 15, 47, 9, 741521) - datetime.datetime(2
    ...: 017, 12, 7, 15, 43, 27, 297769)
Out[34]: datetime.timedelta(0, 222, 443752)

si appliquer l'opération sur __str__

In [35]: '2017-12-07 15:43:14.002752' - '2017-12-07 15:41:14.002752'
TypeError: unsupported operand type(s) for -: 'str' and 'str'

Retourne rien mais erreur.

Un autre exemple.

In [36]: str('string_body')
Out[36]: 'string_body' # in string form

In [37]: repr('real_body')
Out[37]: "'real_body'" #its real body hide inside

J'espère que cela vous aidera à construire des bases concrètes pour explorer plus de réponses.

2
Algebra

__repr__ est utilisé partout, sauf par les méthodes print et str (lorsqu'un __str__ est défini!)

1
techkuz
  1. __str__ doit renvoyer un objet chaîne alors que __repr__ peut renvoyer n'importe quelle expression python.
  2. Si l'implémentation __str__ est manquante, la fonction __repr__ est utilisée comme solution de secours. Il n'y a pas de solution de rechange si l'implémentation de la fonction __repr__ est manquante.
  3. Si la fonction __repr__ renvoie une représentation sous forme de chaîne de l'objet, nous pouvons ignorer l'implémentation de la fonction __str__.

Source: https://www.journaldev.com/22460/python-str-repr-functions

0
Sampath

__str__ peut être appelé sur un objet en appelant str(obj) et devrait renvoyer une chaîne lisible par l'homme.

__repr__ peut être appelé sur un objet en appelant repr(obj) et doit renvoyer un objet interne (champs/attributs d'objet)

Cet exemple peut aider:

class C1:pass

class C2:        
    def __str__(self):
        return str(f"{self.__class__.__name__} class str ")

class C3:        
    def __repr__(self):        
         return str(f"{self.__class__.__name__} class repr")

class C4:        
    def __str__(self):
        return str(f"{self.__class__.__name__} class str ")
    def __repr__(self):        
         return str(f"{self.__class__.__name__} class repr")


ci1 = C1()    
ci2 = C2()  
ci3 = C3()  
ci4 = C4()

print(ci1)       #<__main__.C1 object at 0x0000024C44A80C18>
print(str(ci1))  #<__main__.C1 object at 0x0000024C44A80C18>
print(repr(ci1)) #<__main__.C1 object at 0x0000024C44A80C18>
print(ci2)       #C2 class str
print(str(ci2))  #C2 class str
print(repr(ci2)) #<__main__.C2 object at 0x0000024C44AE12E8>
print(ci3)       #C3 class repr
print(str(ci3))  #C3 class repr
print(repr(ci3)) #C3 class repr
print(ci4)       #C4 class str 
print(str(ci4))  #C4 class str 
print(repr(ci4)) #C4 class repr
0
prosti