In Python, writing clean, maintainable, and well-documented code is not just encouraged—it’s built into the language. Two standout features that help achieve this are f-strings and docstrings.
This article explores how these features work, why they matter, and how to use them effectively to write expressive and self-documenting Python code.
F-strings
A formatted string literal or f-string is a string literal that is prefixed with 'f' or 'F'. These strings may contain replacement fields, which are expressions delimited by curly braces {}. It’s like a shortcut for making strings with dynamic content.
F-strings were introduced in Python version 3.6 . Before Python 3.6, you had two main tools for interpolating values, variables, and expressions inside string literals:
- The string interpolation operator (%), or modulo operator
- The str.format() method
The Modulo Operator(%)
The modulo operator (%) was the first tool for string interpolation and formatting in Python and has been in the language since the beginning. Here’s what using this operator looks like in practice:
name = "Jane"
age = 25
"Hello, %s! You're %s years old." % (name, age)
Output
Hello, Jane! You're 25 years old.
In this example, we use a tuple of values as the right-hand operand to %. Note that we've used a string and an integer. Because you use the %s specifier, Python converts both objects to strings.
The str.format() Method
The str.format() method is an improvement compared to the % operator because it fixes a couple of issues and supports the string formatting mini-language. With .format(), curly braces delimit the replacement fields.
name = "Jane"
age = 25
"Hello, {}! You're {} years old.".format(name, age)
The example above outputs:
"Hello, Jane! You're 25 years old."
F-strings
F-strings joined the party in Python 3.6 with PEP 498. With f-strings, you can easily insert values or expressions directly into the string without much fuss. While other string literals always have a constant value, formatted strings are really expressions evaluated at run time. The parts of the string outside curly braces are treated literally, except that any doubled curly braces '{{' or '}}' are replaced with the corresponding single curly brace. A single opening curly bracket '{' marks a replacement field, which starts with a Python expression.
So, f-strings are like a simple way to put variables or expressions into your strings without having to use complicated formatting methods. It’s just a more straightforward way to make your strings dynamic and flexible.
Here's a simple example of using f-strings in Python:
name = "Jane"
age = 25
print(f"My name is {name} and I am {age} years old.")
In the example, f"My name is {name} and I am {age} years old." is an f-string — a string prefixed with f that allows expressions inside curly braces {} to be evaluated and included directly in the output.
{name} is replaced with the value "Jane".
{age} is replaced with the value 25.
Thus, the output is:
My name is Jane and I am 25 years old.
Notice how readable and concise the string is now that we’re using the f-string syntax. You don’t need operators or methods anymore. You just embed the desired objects or expressions in your string literal using curly brackets.
You can embed almost any Python expression in an f-string. This allows you to do some nifty things like the following:
name = "Jane"
age = 25
f"Hello, {name.upper()}! You're {age} years old."
In the example above, you embed a call to the .upper() string method in the first replacement field. Python runs the method call and inserts the uppercased name into the resulting string. The output will be:
Hello, JANE! You're 25 years old.
F-strings are a bit faster than both the modulo operator (%) and the .format() method, thus are a better method to use when working with strings.
More detailed info on f-strings can be found here: https://docs.python.org/3/reference/lexical_analysis.html#f-strings
DocStrings
A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. It is used to explain what something does and can be accessed via the .doc attribute. All modules should normally have docstrings, and all functions and classes exported by a module should also have docstrings. String literals occurring elsewhere in Python code may also act as documentation. They are not recognized by the Python bytecode compiler and are not accessible as runtime object attributes (i.e. not assigned to doc).
Conventions for writing clear, consistent docstrings are outlined in PEP 257. They include:
- Using triple quotes ("""Docstring""") even for one-liners.
- Starting with a summary line.
- Following the summary with a more detailed description (optional).
- Keeping the summary in the imperative mood (e.g., "Return the sum..." rather than "Returns...").
One-Line Docstrings
Ideal for simple functions or methods, a one-line docstring should:
- Be enclosed in triple double quotes (""").
- Fit on a single line.
- Begin with a capital letter and end with a period.
Example
def add(x, y):
"""Return the sum of x and y."""
return x + y
Multi-Line Docstrings
Multi-line docstrings start with a one-line summary, followed by a blank line, then a detailed description. The summary should be concise enough for indexing tools and clearly separated. The entire docstring should match the indentation of the code it documents.
Docstrings should always be followed by a blank line, especially in classes, to visually separate them from methods. Avoid using uppercase argument names in docstrings; list each argument on its own line using correct case-sensitive identifiers.
Different constructs have tailored docstring needs:
- Scripts: Docstrings should serve as usage/help messages, explaining syntax, arguments, and environment variables.
- Modules: Should list exported objects (classes, functions, etc.) with brief summaries.
- Functions/Methods: Must describe behavior, arguments, return values, side effects, exceptions, and constraints.
- Classes: Should summarize behavior, public members, and subclassing interfaces. If subclassing is involved, describe differences and use “override” or “extend” where appropriate.
Example
def complex(real=0.0, imag=0.0):
"""Form a complex number.
Keyword arguments:
real -- the real part (default 0.0)
imag -- the imaginary part (default 0.0)
"""
if imag == 0.0 and real == 0.0:
return complex_zero
...
Comments vs Docstrings
Comments are descriptions that help programmers better understand the intent and functionality of the program. They are completely ignored by the Python interpreter.
As mentioned above, Python docstrings are strings used right after the definition of a function, method, class, or module. They are used to document our code.
We can access these docstrings using the doc attribute, for example:
class Person:
"""
A class to represent a person.
...
Attributes
----------
name : str
first name of the person
surname : str
family name of the person
age : int
age of the person
Methods
-------
info(additional=""):
Prints the person's name and age.
"""
def __init__(self, name, surname, age):
"""
Constructs all the necessary attributes for the person object.
Parameters
----------
name : str
first name of the person
surname : str
family name of the person
age : int
age of the person
"""
self.name = name
self.surname = surname
self.age = age
def info(self, additional=""):
"""
Prints the person's name and age.
If the argument 'additional' is passed, then it is appended after the main info.
Parameters
----------
additional : str, optional
More info to be displayed (default is None)
Returns
-------
None
"""
print(f'My name is {self.name} {self.surname}. I am {self.age} years old.' + additional)
Here, we can use the following code to access only the docstrings of the Person class:
print(Person.__doc__)
Output
A class to represent a person.
...
Attributes
----------
name : str
first name of the person
surname : str
family name of the person
age : int
age of the person
Methods
-------
info(additional=""):
Prints the person's name and age
We can also use the help() function to read the docstrings associated with various objects. Using the previous example, we can use the help function on the person class as:
help(Person)
Output
Help on class Person in module __main__:
class Person(builtins.object)
| Person(name, surname, age)
|
| A class to represent a person.
|
| ...
|
| Attributes
| ----------
| name : str
| first name of the person
| surname : str
| family name of the person
| age : int
| age of the person
|
| Methods
| -------
| info(additional=""):
| Prints the person's name and age.
|
| Methods defined here:
|
| __init__(self, name, surname, age)
| Constructs all the necessary attributes for the person object.
|
| Parameters
| ----------
| name : str
| first name of the person
| surname : str
| family name of the person
| age : int
| age of the person
|
| info(self, additional='')
| Prints the person's name and age.
|
| If the argument 'additional' is passed, then it is appended after the main info.
|
| Parameters
| ----------
| additional : str, optional
| More info to be displayed (default is None)
|
| Returns
| -------
| None
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
Here, we can see that the help() function retrieves the docstrings of the Person class along with the methods associated with that class.
We can write docstring in many formats like the reStructured text (reST) format, Google format or the NumPy documentation format. To learn more, visit https://stackoverflow.com/questions/3898572/what-is-the-standard-python-docstring-format
We can also generate documentation from docstrings using tools like Sphinx. To learn more, visit https://www.sphinx-doc.org/en/master/
Top comments (0)