Last Updated: June 01, 2026
Beginner
The need to print when you program is of course one of the most important, and probably the very first things you ever did! This is your full guide on how to print for both python 2 and python 3).
The quickest and simplest scenario on how to print is to simply write the following:
print("Hello World")

However, there are many other variations of printing that comes up when you are coding in python. These could be printing json files, printing without a new line, printing to a log file, printing formatted text, and many more. Find below what you’re looking for in this one stop guide to printing!
Python developer and educator with 15+ years building production systems across data engineering, web APIs, and AI tooling. Founder of Python How To Program — 270+ in-depth tutorials covering the modern Python stack.
Printing without a new line
When you normally use the print(“abc”) construct it still adds a new line character. In order to print without a new line use the end parameter.
print("Hello World", end="")
Normally when printing:

See example with the print parameter:

Printing Text together
When printing items, there are often times you need to print text write next to each other, or you need concatenate text together. Concatenating text in Python is simple and can be done in several ways.
Note that in the below approach that for the method 2, there is no space between the text which is why method 3 helps to solve this problem.
text1 = 'shoe'
text2 = 'laces'
print("Method 1:", text1, text2)
print("Method 2:", text1 + text2)
print("Method 3:", text1 + ' ' + text2)
print("Method 4:", "%s %s" % ( text1, text2) )

Formatting numeric output when printing
When printing, often it’s needed to format the print output. Here’s a list of formatting scenarios.
Printing a number with a string
When printing a number, it is typically simple to do with the following statement:
counter = 5
print(counter)

The problem arises when you want to print text along the same line. You will typically get this error TypeError: unsupported operand type(s) for +: ‘int’ and ‘str’ . For example for the following:

The trick is that you can always concatenate two strings together. Hence, you simply need to convert the int (short for integer, or whole number) into a str (string).
counter = 5
print( str(counter) + ' apples' )

Padding zeros when printing numbers
When printing numbers, often you need to pad with zeros. There are multiple ways to do this, but one of the easier ways is to first convert the number to a string, and then use the zfill function of the string where you can specify how long the number should be.
#this prints the number 10 with up to 8 padded zeros
counter = 10
str(counter).zfill(8)
A more advanced example follows where we’re printing 7 numbers. Notice that for the last number where the number is more than 8 digits, that there are no padded zeros.
counter = 2
for x in range(1, 7):
print( str(counter).zfill(8) )
counter = counter * counter

Another method to pad zeros is the following method to use the format function where a zero is placed in front of the number of digits. Here the “08” refers to padding zeros for 8 digits
counter = 2
for x in range(1, 7):
print( format(counter, '08'))
counter = counter * counter

Printing with text alignment
The following can be used when you want to print a table of contents where the structure “{:>nn”.format(‘text to format’) is used. nn is the number of letters to pad.
'{:>15}'.format('text')
Without any alignment:

With alignment:

Printing complex data structures in readable format
One of the great things about python is that you can put together complex data structures fairly easily. This could be a dictionary where each dictionary item is a list. However, to print this out normally is quite difficult to read. This is where pretty print comes in. Suppose you have the following structure:

Within python, this is represented as a dictionary where the main items “furniture” and “appliances” have the sub-items. So if the data structure is “listitems”, then the data coudl be represented as follows:
listitems ={ 'furniture':[ 'desk', 'chair', 'sofa'], 'appliances':['tv', 'lamp', 'hifi']}
With this in mind, then printing of this data would be as follows:
listitems ={ 'furniture':[ 'desk', 'chair', 'sofa'], 'appliances':['tv', 'lamp', 'hifi']}
print(listitems)

This is where the import library pprint comes in. You can simply use this to print out the output in a more readable fashion. There are two important parameters though. You should use the indent parameter to specify how much space there is per element, and then width to ensure that limited items are put on a single two. If you put a width of 1 character, then that’ll ensure only show one element at most (so if a element has more than 1 character it’s ok, but you cannot include a second element in there as you’re already the 1 width limit).
import pprint;
listitems ={ 'furniture':[ 'desk', 'chair', 'sofa'], 'appliances':['tv', 'lamp', 'hifi']}
pprint.pprint(listitems, indent=1, width=1)

Printing time
Printing time is another important item that you tend to do often in case you want to monitor performance or perhaps to give an update that your long operation is still running.
Print the time
First lets simply print the current date and time
import datetime
print(datetime.datetime.now())

This date time can be easily formatted using the special function from the date object “strftime”. With strftime you can convert the format of the time quite easily to a specified format of hours, mins, seconds and date, with or without the timezone information
import datetime
currentTime = datetime.datetime.now()
print(currentTime.strftime("%Y-%m-%d"))
print(currentTime.strftime("%Y-%m-%d %H:%M:%S"))
print(currentTime.strftime("%Y-%m-%d %H:%M:%S %Z%z"))

As you can guess the Y=year, m = month, d = year, H = hour, M=minutes, S = seconds. Z = timezone. You’ll notice that for the 3rd print item the timezone is blank. We’ll address that in the next section.
Print the time in the correct timezone
However, if you are using a remote machine, or a virtual machine where your local timezone is not set, you may want to chose your own timezone. Also, if you are running services which are across different machines, it is important to make sure you use the right timezone. One simple way is to use universal time (UTC), or to simply to set a single timezone. You can then convert as required.
import datetime
import pytz #include the timezone module
currentTime = datetime.datetime.now( pytz.timezone('UTC') )
print(currentTime.strftime("%Y-%m-%d"))
print(currentTime.strftime("%Y-%m-%d %H:%M:%S"))
print(currentTime.strftime("%Y-%m-%d %H:%M:%S %Z%z"))

Please note in the above example that when the timezone format was shown it showed that the timezone was set to UTC+0000 unlike the previous example. This means that the timezone information was present there.
In the following code, we will first get the time in the UTC timezone, and then convert the time to Hong Kong timezone.
import datetime
import pytz
currentTime = datetime.datetime.now( pytz.timezone('UTC') )
print("Time 1 (UTC time):", currentTime.strftime("%Y-%m-%d %H:%M:%S %Z%z"))
now_local = currentTime.astimezone(pytz.timezone('Asia/Hong_Kong'))
print("Time 2a(HK time) :", now_local.strftime("%Y-%m-%d %H:%M:%S %Z%z"))
print("Time 2b(HK time) :", now_local.strftime("%Y-%m-%d %H:%M:%S "))

Please note that in the “Time 2a” output, you can see the Hong Kong time as 2am with the timezone indicator at the end of +8 hours. The final “Time 2b” is the same time without the timezone included.
Finally, you can get a list of all the timezones available with a quick check on the pytz module and checking “all_timezones”.
import pytz
for tz in pytz.all_timezones:
print(tz)

How to print an exception
Things will go wrong in your code all the time – especially things that you don’t expect. This is where exceptions come in where the try exception blocks fit in quite nicely. The tricky part is that you need to make sure you output what the exception is in order for you to understand what’s going on.
Firstly a quick example of where a try /except can be helpful. Suppose you had the following code where after the definition of the function, the function was called.
def badFunction():
print(a) #print an undefined function
badFunction()#call the functionprint("have a nice day")

In here, as the variable “a” was not defined, then the program terminated and the final line “have a nice day” was never printed.
This is where try/except blocks can come in where you can catch errors from uncertain actions. So you can wrap the “badfunction” in a try block. See following example:
def badFunction():
print(a)
#Try the unsafe code
try:
badFunction()
except NameError:
print("Variable x is not defined")
except:
print("Something else went wrong")
print("have a nice day")

Here, the program continued to run gracefully and it caught the exception with the error “Variable x is not defined”. The reason it was caught was due to “NameError” exception object being defined.
In this next example, we have put a different error. Now the variable is defined as a number but there will be an exception as the number will be concatenated to a string.
def badFunction():
a = 1
print(a + ' join str') #this will fail as joining a string with a number
#try the unsafe code
try:
badFunction()
except NameError:
print("Variable x is not defined")
except:
print("Something else went wrong")
print("have a nice day")

Here another exception was caught but with the generic message of “Something else went wrong”. This is where printing the actual exception is really important. This is where you can define the exception object and print out the error.
ef badFunction():
a = 1
print(a + ' join str')
try:
badFunction()
except NameError:
print("Variable x is not defined")
except Exception as e:
print(e) #print the exception object print("have a nice day")

Here you can see that the reason for the failure was included, and the program continued to run.
There’s a final improvement we can make which is to include where the problem occurred. This is really important where you have logging defined and you can see where the issue was caused.
import traceback
def badFunction():
a = 1
print(a + ' join str')
#run the unsafe code
try:
badFunction()
except NameError:
print("Variable x is not defined")
except Exception as e:
print(e)
traceback.print_tb(e.__traceback__) #show the call list
print("have a nice day")

Here you can see the error description “Unsupported operand type(s) for +”, and then also where the error occurred from the initial call on line 8 with the call to “badFunction()” and the actual offending line of line 5.
Many more printing on python
There’s many more ways to print outputs within python, however this was intended to be a simple resource for some of the common printing challenges that come up, how you can use them, and with a simple example to get you up to speed very quickly with usable code. More to come!
Subscribe to our newsletter
How To Use Python ward for Modern Python Testing
Intermediate
You have written enough test_something functions to last a lifetime. Your test files are full of names like test_user_creation_when_email_is_valid_and_age_is_over_18 — forty characters of snake_case just to describe one behaviour. When a test fails, the output tells you the function name but not what the test was actually checking. You end up reading the function body to figure it out, which defeats the point of a test name entirely.
Python’s ward library takes a different approach. Instead of naming test functions, you describe them — passing a plain English string to the @test decorator. When a test fails, ward prints exactly what it was supposed to do, in the language you wrote it, not a slug-cased identifier. The library also ships with a fixture system built on function arguments, a fluent expect() assertion API, and parameterized tests that read like a table of examples. Install it with pip install ward — no extra dependencies.
This article walks you through everything you need to use ward effectively. We will cover writing your first tests with the @test decorator, organizing shared setup with @fixture, running parameterized test cases, using the expect() assertion chain, and building a real-world test suite for a small utility library. By the end, you will have a working test suite and a clear picture of where ward fits against pytest.
Writing a ward Test: Quick Example
Before diving into the details, here is a minimal ward test file you can run right now. Create a file called test_math.py and paste in the following:
# test_math.py
from ward import test
@test("adding two positive integers returns their sum")
def _():
assert 1 + 2 == 3
@test("dividing by zero raises ZeroDivisionError")
def _():
try:
_ = 10 / 0
assert False, "should have raised"
except ZeroDivisionError:
pass
Run it with:
ward
Output:
PASS test_math adding two positive integers returns their sum
PASS test_math dividing by zero raises ZeroDivisionError
2 passed in 0.02 seconds
Two things stand out immediately. First, every test function is named _ — the name is irrelevant because ward uses the string you pass to @test instead. Second, the output reads like a sentence, not a mangled identifier. Those two changes alone make failing tests much easier to diagnose at a glance.
The sections below explain each ward feature in depth and show you how to apply them to realistic code.
What Is ward and Why Use It?
Ward is a Python testing framework designed to make test code more readable and test output more useful. It was created as an alternative to pytest and unittest, borrowing the best ideas from both while rethinking the parts that have always felt awkward — especially test naming and fixture injection.
The central idea is that a test is a fact you assert about your code, and that fact deserves to be written in human language. In pytest you write def test_cart_total_is_zero_when_no_items_added():. In ward you write @test("cart total is zero when no items are added"). The string is the documentation; the function body is the proof.
Here is how ward compares to pytest on the features most Python developers care about:
| Feature | pytest | ward |
|---|---|---|
| Test description | Function name (snake_case) | Plain English string |
| Fixtures | @pytest.fixture | @fixture with argument injection |
| Parameterization | @pytest.mark.parametrize | @using with each() |
| Assertions | assert (with rewriting) | assert or fluent expect() |
| Output format | Dots, F, E characters + tracebacks | Coloured PASS/FAIL lines with descriptions |
| Installation | pip install pytest | pip install ward |
Ward is not trying to replace pytest in every project — it is a deliberate choice that pays off most when you want test output to function as living documentation. If you share test runs with non-developers or treat CI output as a changelog, ward’s readable output earns its place immediately.
Installing ward
Ward requires Python 3.6 or later. Install it into your project’s virtual environment:
# install.sh
pip install ward
Verify the installation:
# verify_install.py
import ward
print(ward.__version__)
Output:
0.68.0b0
Ward discovers tests automatically. By default it searches for any file matching the pattern test_*.py in the current directory and its subdirectories — the same convention used by pytest. You can override the search path:
# Run all tests in a specific directory
ward --path tests/
# Run tests matching a keyword in their description
ward --search "total"
# Run tests in a single file
ward --path tests/test_cart.py
There is no configuration file required to get started. For larger projects, ward reads from a pyproject.toml [tool.ward] section if one exists.
Writing Tests with @test
The @test decorator is the foundation of every ward test suite. It takes a single string argument that describes what the test is verifying. The decorated function’s name is ignored entirely — the convention is to name every test function _ to make that clear.
# test_string_utils.py
from ward import test
def shout(text):
"""Convert text to uppercase with an exclamation mark."""
return text.upper() + "!"
@test("shout converts text to uppercase")
def _():
assert shout("hello") == "HELLO!"
@test("shout appends an exclamation mark")
def _():
result = shout("ward")
assert result.endswith("!")
@test("shout works on an already uppercase string")
def _():
assert shout("PYTHON") == "PYTHON!"
Output:
PASS test_string_utils shout converts text to uppercase
PASS test_string_utils shout appends an exclamation mark
PASS test_string_utils shout works on an already uppercase string
3 passed in 0.01 seconds
When a test fails, ward shows you the description, the file and line number, and the values that caused the failure. You get the “what failed” and “what were the values” without reading the traceback from the bottom up. That is a significant quality-of-life improvement when you have a large test suite and a CI run with dozens of failures.
Shared Setup with @fixture
Most tests need shared setup — a database connection, a sample data structure, or a configured object. Ward handles this with the @fixture decorator, which works very similarly to pytest fixtures. You declare a fixture function, then inject it into your test by using the fixture function as a default argument.
# test_user.py
from ward import test, fixture
@fixture
def sample_user():
return {
"name": "Alice",
"email": "alice@example.com",
"age": 25,
"active": True,
}
@test("user name is a string")
def _(user=sample_user):
assert isinstance(user["name"], str)
@test("user age is a positive integer")
def _(user=sample_user):
assert user["age"] > 0
@test("inactive users cannot be created with this fixture")
def _(user=sample_user):
assert user["active"] is True
Output:
PASS test_user user name is a string
PASS test_user user age is a positive integer
PASS test_user inactive users cannot be created with this fixture
3 passed in 0.01 seconds
The fixture function runs fresh for each test that uses it — there is no shared state between tests unless you explicitly use a module-level or session-level scope. To add teardown logic, use yield inside the fixture:
# test_file_fixture.py
import os
import tempfile
from ward import test, fixture
@fixture
def temp_file():
# Setup: create a temporary file
fd, path = tempfile.mkstemp(suffix=".txt")
os.close(fd)
with open(path, "w") as f:
f.write("hello ward")
yield path
# Teardown: delete the file after the test
if os.path.exists(path):
os.remove(path)
@test("temp file exists on disk")
def _(path=temp_file):
assert os.path.isfile(path)
@test("temp file contains the expected text")
def _(path=temp_file):
with open(path) as f:
content = f.read()
assert content == "hello ward"
Output:
PASS test_file_fixture temp file exists on disk
PASS test_file_fixture temp file contains the expected text
2 passed in 0.03 seconds
The code after yield runs automatically after each test that uses temp_file. Ward handles the teardown even if the test throws an exception, so you do not need try/finally blocks in your test functions.
Parameterized Tests with @using and each()
Parameterized tests let you run the same logic against multiple inputs without duplicating code. Ward’s approach is cleaner than pytest’s marker syntax — you use the @using decorator combined with the each() helper to define the value sets.
# test_math_ops.py
from ward import test, each, using
def clamp(value, lo, hi):
"""Clamp value to the range [lo, hi]."""
return max(lo, min(hi, value))
@test("clamp({value}, {lo}, {hi}) returns {expected}")
@using(
value=each(5, -3, 100, 50),
lo=each(0, 0, 0, 0),
hi=each(10, 10, 10, 10),
expected=each(5, 0, 10, 10),
)
def _(value, lo, hi, expected):
assert clamp(value, lo, hi) == expected
Output:
PASS test_math_ops clamp(5, 0, 10) returns 5
PASS test_math_ops clamp(-3, 0, 10) returns 0
PASS test_math_ops clamp(100, 0, 10) returns 10
PASS test_math_ops clamp(50, 0, 10) returns 10
4 passed in 0.01 seconds
Notice that ward interpolates the parameter values into the test description for each run. When one case fails, the output tells you exactly which input caused the problem — clamp(100, 0, 10) returns 10 is far more useful than test_clamp_params[2].
The each() values are matched positionally — the first value from every each() forms the first test case, the second values form the second, and so on. All each() calls in one @using decorator must have the same length.
Fluent Assertions with expect()
Ward provides an optional fluent assertion API called expect(). Instead of writing bare assert statements, you chain methods that read like English and produce more specific error messages when they fail. Use whichever style you prefer — both work the same way under the hood.
# test_expect.py
from ward import test, expect
@test("expect: integer is within range")
def _():
expect(42).to_be_greater_than(40)
expect(42).to_be_less_than(50)
@test("expect: string contains a substring")
def _():
expect("pythonhowtoprogram").to_contain("python")
@test("expect: list has expected length")
def _():
items = ["a", "b", "c"]
expect(items).has_length(3)
@test("expect: dictionary contains a key")
def _():
config = {"debug": True, "timeout": 30}
expect(config).to_contain_key("debug")
@test("expect: value is an instance of a type")
def _():
expect(3.14).to_be_instance_of(float)
Output:
PASS test_expect expect: integer is within range
PASS test_expect expect: string contains a substring
PASS test_expect expect: list has expected length
PASS test_expect expect: dictionary contains a key
PASS test_expect expect: value is an instance of a type
5 passed in 0.02 seconds
When an expect() assertion fails, ward reports both the expected condition and the actual value on separate lines. For example, if expect(15).to_be_greater_than(40) fails, the output reads Expected 15 to be greater than 40 — no need to open the test file and read the assert statement.
The expect() API supports chaining, so you can write several assertions about the same value in sequence. This is useful when a single test function validates multiple properties of one object, and you want each failure to be described independently.
Real-Life Example: Testing a Shopping Cart
Let us build a test suite for a simple shopping cart module using everything covered above: fixtures for shared state, parameterized tests for pricing logic, and expect() for readable assertions.
First, the module under test:
# cart.py
class Cart:
"""A simple shopping cart with item management and total calculation."""
def __init__(self):
self._items = {} # name -> {"price": float, "qty": int}
def add(self, name, price, qty=1):
if name in self._items:
self._items[name]["qty"] += qty
else:
self._items[name] = {"price": price, "qty": qty}
def remove(self, name):
self._items.pop(name, None)
def total(self, tax_rate=0.0):
subtotal = sum(v["price"] * v["qty"] for v in self._items.values())
return round(subtotal * (1 + tax_rate), 2)
def item_count(self):
return sum(v["qty"] for v in self._items.values())
def is_empty(self):
return len(self._items) == 0
Now the ward test suite:
# test_cart.py
from ward import test, fixture, expect, each, using
from cart import Cart
# ----- Fixtures -----
@fixture
def empty_cart():
return Cart()
@fixture
def stocked_cart():
c = Cart()
c.add("apple", price=0.99, qty=3)
c.add("bread", price=2.49)
c.add("milk", price=1.75, qty=2)
return c
# ----- Basic behaviour -----
@test("a new cart is empty")
def _(cart=empty_cart):
expect(cart.is_empty()).to_be_truthy()
expect(cart.item_count()).equals(0)
@test("adding an item increases item count")
def _(cart=empty_cart):
cart.add("apple", price=0.99, qty=4)
expect(cart.item_count()).equals(4)
@test("adding the same item twice increases quantity not entry count")
def _(cart=empty_cart):
cart.add("apple", 0.99, qty=1)
cart.add("apple", 0.99, qty=2)
expect(cart.item_count()).equals(3)
@test("removing an item reduces total")
def _(cart=stocked_cart):
before = cart.total()
cart.remove("bread")
expect(cart.total()).to_be_less_than(before)
# ----- Total calculation (parameterized) -----
@test("total with {rate*100:.0f}% tax is {expected}")
@using(
rate=each(0.0, 0.1, 0.2),
expected=each(9.46, 10.41, 11.35),
)
def _(cart=stocked_cart, rate=None, expected=None):
# stocked: 3*0.99 + 1*2.49 + 2*1.75 = 9.46 subtotal
expect(cart.total(tax_rate=rate)).equals(expected)
Output:
PASS test_cart a new cart is empty
PASS test_cart adding an item increases item count
PASS test_cart adding the same item twice increases quantity not entry count
PASS test_cart removing an item reduces total
PASS test_cart total with 0% tax is 9.46
PASS test_cart total with 10% tax is 10.41
PASS test_cart total with 20% tax is 11.35
7 passed in 0.03 seconds
The test suite demonstrates three things at once: the stocked_cart fixture gives each test a fresh, pre-populated cart without any shared mutation; the parameterized tax tests cover three pricing scenarios without duplicated code; and the expect() calls make each assertion readable without a comment explaining what is being checked. To extend the example, try adding tests for negative quantities, zero-price items, or a discount() method — the fixture and parameterization patterns scale without any structural changes.
Frequently Asked Questions
Can ward tests coexist with pytest tests in the same project?
Yes, but they run in separate commands. Ward discovers test_*.py files using the same convention as pytest, but the two runners are independent — running ward only executes tests decorated with @test, and running pytest only executes functions prefixed with test_. If you are migrating gradually, you can keep both frameworks active during the transition. Ward can also use pytest fixtures defined in a conftest.py file, which eases migration considerably.
How do fixture scopes work in ward?
Ward fixtures support four scopes: Scope.Test (default, reruns for every test), Scope.Module (reruns once per test file), Scope.Global (reruns once per entire test run), and Scope.Call (same as Test). Set the scope with @fixture(scope=Scope.Module). Use module or global scope for expensive operations like database connections or API clients that you do not want to recreate thousands of times.
Should I use expect() or plain assert statements?
Both work correctly and produce useful output. The expect() API gives you method names that double as documentation — expect(value).to_be_greater_than(10) is self-explanatory even in a code review. Plain assert is faster to type and feels more natural if you are coming from pytest. A common middle ground: use assert for straightforward equality checks and expect() when comparing ranges, types, or collection membership where the error message matters most.
How do I run a single test or a subset of tests?
Use the --search flag to filter by description substring: ward --search "cart total" will run every test whose description contains “cart total”. You can also use --path tests/test_cart.py to restrict the discovery scope to a single file. For more precise control, ward supports tags via @test("...", tags=["slow"]) combined with ward --tags slow on the command line.
Does ward support async tests?
Yes. Ward automatically detects async def test functions and fixture functions and runs them using asyncio.run(). You do not need any plugin or marker — just write async def _(): and ward handles the event loop. This works the same way for fixtures, so an async fixture can await a database connection and yield it to an async test without any additional setup.
How do I get code coverage with ward?
Run ward under coverage using the standard coverage.py wrapper: coverage run -m ward. After the run completes, generate the report with coverage report or coverage html for a browsable HTML breakdown. Ward does not bundle its own coverage tool — it relies on coverage.py, which is the same approach pytest recommends and integrates cleanly with CI pipelines and codecov.io.
Conclusion
Python ward replaces the test-naming ceremony of traditional frameworks with something more direct: a plain English description that appears in every test report, every CI log, and every team standup where someone asks “what broke?” The @test decorator, @fixture injection, @using parameterization, and the expect() assertion chain all work together to make tests read like specifications and failures read like bug reports.
The shopping cart example in this article covers the core patterns you will reach for in a real project. Try extending it — add a coupon fixture that returns a discount function, write parameterized tests for boundary cases like zero-quantity items, or switch the Cart storage to a database and test the async fixture pattern. Each of those extensions fits naturally into the structure already in place.
For the full API reference, see the official ward documentation at ward.readthedocs.io. The project is also actively maintained on GitHub at github.com/darrenburns/ward, where you can find the changelog and open issues.
Related Articles
Further Reading: For more details, see the Python print() function documentation.
Frequently Asked Questions
What does \n do in Python print statements?
The \n escape sequence creates a newline character, causing text after it to appear on the next line. For example, print('Hello\nWorld') outputs ‘Hello’ and ‘World’ on separate lines.
How do I print multiple lines without using \n?
You can use triple-quoted strings (''' or """) to write multi-line text directly, or call print() multiple times. The textwrap.dedent() function also helps format multi-line strings cleanly.
What is a format exception in Python?
A format exception (typically a ValueError) occurs when a format string and its arguments do not match. For example, using the wrong number of placeholders in str.format() or mismatched types in f-strings.
How do I use f-strings for text formatting in Python?
F-strings (formatted string literals) use the syntax f'text {variable}' and were introduced in Python 3.6. They allow you to embed expressions directly inside string literals for readable, efficient formatting.
What is the difference between print() and sys.stdout.write()?
print() adds a newline by default and accepts multiple arguments with separators. sys.stdout.write() writes raw text without any automatic newline, giving you more control over output formatting.
Related Articles
- How To Print String in Color in Python
- Python Type Hints for Better Code
- How To Read and Write JSON Files
Continue Learning Python
Tutorials you might also find useful:
- How To Use Python textwrap Module for Text Formatting
- How To Format Python Code with Black and isort
- How To Use Python difflib for Comparing Text and Sequences
- How To Use Python tabulate for Pretty-Printing Tables
- Simple Guide To Markov Chain Text Generator in Python 3
- Reading and writing text to files in Python