Are they the only method through which we can print object attributes?
No! That would be a brute limitation harming the beauty of OOP. Of course you can define your own function, even functions, to print whichever attributes you want in any format you like. The thing with that is, you'll have to call it everywhere you need... which brings us to your next question:
What's the advantage of repr
or str
over this?
The advantage is that they are built-in special functions that can be overriden and are called by Python behind the scenes when necessary. The most obvious examples are: __str__
is called when you call print
on your object. __repr__
is called when you try to inspect your object in the console. A little example:
class Foo:
def __init__(self, arg):
self.x = arg
def display(self):
return "This is our own way of using {}!".format(self.x)
def __str__(self):
return "This is our great object using {}".format(self.x)
def __repr__(self):
return "Foo(arg={})".format(self.x)
class Bar:
pass
If we run this:
>>> foo = Foo(3)
>>> print(foo)
This is our great object using 3
>>> foo.display()
'This is our own way of using 3!'
>>> foo
Foo(arg=3)
Note that we didn't explicitly call __str__
or __repr__
but we can clearly see that our implementation was printed out. This is again, because those are special functions that are being called in the background by Python.
As a comparison, this would be the output if we didn't implement any of those:
>>> bar = Bar()
>>> print(bar)
<__main__.Bar object at 0x000001E20F2D1040>
>>> bar
<__main__.Bar object at 0x000001E20F2D1040>
>>> bar.display()
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Bar' object has no attribute 'display'
I narrowed the scope of my answer to your question regarding our own defined functions, to read a more detailed explanation of the full roles and differences between __str__
and __repr__
read this question.