Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
When you use the variable without any format specifier (print(f'{variable}')), internally its __str__ method is called. Considering your Test class, it already has this method: class Test: def...
Answer
#1: Initial revision
When you use the variable without any format specifier (`print(f'{variable}')`), internally its `__str__` method is called. Considering your `Test` class, it already has this method: ```python class Test: def __init__(self, valor): self.valor = valor def __str__(self): print('calling __str__') # calling print just to show this method is called return f'Test({self.valor})' t = Test(42) print(f'{t}') ``` So the output is: ```none calling __str__ Test(42) ``` But what it's **actually** being called behind the scenes is the `__format__` method. [According to the documentation](https://docs.python.org/3/reference/datamodel.html#object.__format__), `__format__` is called when the object is being evaluated inside a [*f-string*](https://docs.python.org/3/reference/lexical_analysis.html#f-strings), or when passed as an argument to [`str.format`](https://docs.python.org/3/library/stdtypes.html#str.format) and the [`format`](https://docs.python.org/3/library/functions.html#format) built-in. But my `Test` class didn't implement `__format__`, so it uses the method inherited from its superclass (`object`). And if we check its implementation in [CPython source code](https://github.com/python/cpython/blob/master/Objects/typeobject.c) - checked in 2020-08-11, comments removed): ```c static PyObject * object___format___impl(PyObject *self, PyObject *format_spec) { if (PyUnicode_GET_LENGTH(format_spec) > 0) { PyErr_Format(PyExc_TypeError, "unsupported format string passed to %.200s.__format__", Py_TYPE(self)->tp_name); return NULL; } return PyObject_Str(self); } ``` This means that, when no format specifier is used (only `f'{variable}'`), it doesn't enter the `if` and returns `PyObject_Str(self)` (and [according to the documentation](https://docs.python.org/3/c-api/object.html#c.PyObject_Str), `PyObject_Str(whatever)` is equivalent to calling `str(whatever)` - which in turn [calls `whatever.__str__()`](https://docs.python.org/3/reference/datamodel.html#object.__str__)). But if I use some format specifier (such as `f'{variable:>10}'`), it enters the `if` and shows the error message ("_unsupported format string etc_"). > _The [docs says](https://docs.python.org/3/reference/datamodel.html#object.__format__) that this behaviour of raising a `TypeError` when there's no format specifier [was implemented in Python 3.4](https://github.com/python/cpython/commit/2cd8ce469021e851f4ab70106d696136ecddcd51#diff-c3cf251f16d5a03a9e7d4639f2d6f998), and the call to `str(self)` was implemented in Python 3.7 (before that, it called `format(str(self), '')`, according to [this commit](https://github.com/python/cpython/commit/7e19dbc92ec06a987eaae72f7cdfd32006aa4960))._ Conclusion: if I want format specifiers to work with my class, I have to implement `__format__`: ```python class Test: def __init__(self, value): self.value = value def __str__(self): print('calling __str__') return f'Test({self.value})' def __format__(self, format_spec): print(f'calling __format__ with: "{format_spec}"') return f'{self.value:{format_spec}}' t = Test(42) print(f'{t}') print(f'{t:>10}') ``` Now both `print` statements work (and I implemented `__format__` in a way that it doesn't delegate to `__str__`, but I could've done it). The output is: ```none calling __format__ with: "" 42 calling __format__ with: ">10" 42 ``` Just reminding that I'll get the same output above using `str.format`: ```python print('{}'.format(t)) print('{:>10}'.format(t)) ``` --- That's why format specifiers don't work with lists and dictionaries, because [`list`][1] e [`dict`][2] don't override `__format__` and use the inherited implementation from `object` (which delegates to `str` when there are no format specifiers, and raises an error when there is - and that's why it works when I do just `print(f'{some_list}')`). Unfortunately we can't implement `__format__` in lists and dictionaries, because [we can't add new methods in native classes](https://stackoverflow.com/a/21320086), so the only solution seems to be to convert them to strings (using `str`, or iterating through its elements and manually building a string). On the other hand, numbers and strings implement `__format__` and work fine with format specifiers. And, as already said, this works not only with *f-strings*, but also with `str.format` and `format` built-in: ```python t = Test(42) # using the last version above, with the implementation of __format__ # both lines below call Test.__format__ print('{:>10}'.format(t)) print(format(t, '>10')) mylist = [1, 2] # both lines below raise TypeError: unsupported format string passed to list.__format__ print('{:>10}'.format(mylist)) print(format(mylist, '>10')) ``` --- By the way, that's why it's possible to format [`datetime`](https://docs.python.org/3/library/datetime.html) classes this way - such as `f'{datetime.now():%d/%m/%Y}'` - because those classes implement `__format__`, delegating to `strftime`. Which means that I could also do something similar with my `Test` class, defining any custom format specifier I want: ```python class Test: # ... constructor, etc def __format__(self, format_spec): formats = { # custom formats (not the best examples) 'whatever' : f'format whatever -> {self.value}', 'another format' : f'another: {self.value}' } if format_spec in formats: return formats[format_spec] return f'{self.value:{format_spec}}' t = Test(42) print(f'{t:whatever}') # format whatever -> 42 print(f'{t:another format}') # another: 42 print(f'{t:>10}') # or #print('{:whatever}'.format(t)) #print('{:another format}'.format(t)) #print('{:>10}'.format(t)) ``` Output: ```none format whatever -> 42 another: 42 42 ``` [1]: https://docs.python.org/3/library/stdtypes.html#list [2]: https://docs.python.org/3/library/stdtypes.html#dict