Skip to content

Output and input

docs.scrimba.com

Two tools you will use from the very first line you write: print() shows values in the terminal, input() gets text from the user. They are simple, but knowing how they behave saves you from a handful of surprises early on.

print() and input() are Python's standard terminal I/O functions. Both are more configurable than they first appear. print() accepts arguments that control how values are joined and where output ends. input() always returns a string, which shapes how you handle every value that comes from a user.

Under the hood, print() writes to sys.stdout and input() reads from sys.stdin (the standard output and input streams your terminal is wired to). Both deal only in text, and output is buffered by default: Python holds it briefly and writes in chunks rather than one character at a time. Two consequences matter in real programs: output goes to a different stream from errors (stdout versus stderr), and you can force that buffer to flush when you need output to appear immediately.

How Python runs your code

Python runs your code from top to bottom, one line at a time, in exactly the order you wrote it. No jumping around. The order you write things is the order they run. Always.

python
city = "Tokyo"
print(city)
print("Population: 14 million")

city gets assigned first. The first print runs. The second print runs. Every time, in that order.

This matters because you cannot use a variable before you have assigned it. Python has not seen it yet and will raise an error:

python
print(country)   # NameError: country is not defined yet
country = "Japan"

Keep this in mind as your programs grow: anything you use must be defined before you use it.

Python uses sequential execution: each statement is evaluated as it is encountered, in a single top-to-bottom pass through the file. There is no pre-scan of the whole file first. Referencing a name before it is assigned raises NameError at exactly that line, not earlier.

python
city = "Tokyo"
print(city)             # works: city is already bound
print(country)          # NameError: not yet assigned
country = "Japan"

The rule: dependencies must be defined before the line that uses them.

Python runs your file in a single top-to-bottom pass, and it only looks up a name at the moment that line actually runs, not when it first reads the file. That is why a missing name is a NameError at runtime (when the line executes) rather than an error the instant you save. There is no hoisting (the way JavaScript quietly moves var declarations to the top of their scope) and no forward declarations (the way C lets you promise a name now and define it later). A name exists the moment it is assigned, and any use before that raises.

python
print(country)   # NameError at runtime, not a syntax error
country = "Japan"
JunoHow Python runs your code Python reads your file top to bottom, one line at a time, in the order you wrote it. The catch that trips everyone once: a name has to exist before the line that uses it, or you get a NameError. No skipping ahead, no reading the whole file first.
JunoHow Python runs your code Top to bottom, one pass, in order. A name has to be assigned before any line that uses it, otherwise it's a NameError at that exact line. No pre-scan of the file.
JunoHow Python runs your code One top-to-bottom pass, names looked up as each line runs. Use a name before it's assigned and you get a NameError at runtime, not a syntax error. Python doesn't hoist and has no forward declarations, so order really is everything.

Printing output

print() is how Python talks back to you. Pass it any value and it displays that value. It converts whatever you give it to text automatically.

print() converts each argument to a string via str(), joins them with a separator (a space by default), then writes the result followed by a newline to standard output. Understanding the defaults makes the optional arguments more predictable.

print()'s full signature is print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False). The *objects collector accepts any number of positional arguments and calls str() on each. sep and end control formatting; file redirects output to any writable stream; flush forces the buffer to drain immediately.

python
print("Hello")    # Hello
print(42)         # 42
print(3.14)       # 3.14
print(True)       # True

Multiple values

You can print multiple values at once by separating them with commas. Python puts a space between them by default. Change the separator with sep:

Multiple positional arguments are each converted with str() individually, then joined by sep. The default sep is a single space. Overriding it lets you produce formatted output without string concatenation:

print() doesn't build one string and write it; it writes each argument to the stream in turn (calling str() on each), with sep between and end last. Because those writes happen one at a time rather than as a single locked operation, two print() calls running at the same time (on different threads) can interleave their output. When that matters, build one string and print it once. For everyday use, overriding sep still beats gluing strings together by hand:

python
name = "Alice"
age = 28
print(name, age)                        # Alice 28
print("Name:", name)                    # Name: Alice
print("2024", "01", "15", sep="-")     # 2024-01-15
print("a", "b", "c", sep=", ")         # a, b, c

Controlling the line ending

Each print() call ends with a newline by default, so the next output starts on a fresh line. Change that with end. Setting end="" makes the next print continue on the same line:

The end parameter replaces the default newline. Set it to "" to suppress the line break, to " " to stay on the same line with a space, or to any other string. Combined with sep, you can produce most output formats without building strings manually:

sep and end are keyword-only (you pass them by name, like end="", never by position), defaulting to a space and a newline. end="" is the standard way to print a partial line and let the next call carry it on. For output that needs to appear as it happens, like a progress indicator or a live log, add flush=True so Python writes immediately instead of waiting for a newline or for its buffer to fill.

python
print("Loading", end="")
print("...")
# Loading...

print("one", end=" | ")
print("two", end=" | ")
print("three")
# one | two | three
JunoPrinting outputprint() turns whatever you give it into text and shows it. Pass several things separated by commas and you get a space between them, change that with sep. Every print drops to a new line at the end; end="" keeps the next one on the same line.
JunoPrinting outputprint() runs str() on each argument, joins them with sep (a space by default), and finishes with end (a newline by default). Override either to format output without gluing strings together.
JunoPrinting outputprint() writes each argument through str(), with sep between and end last, both keyword-only. Reach for end="" to continue a line, and flush=True when the output needs to show up right away.

Formatting output with f-strings

The cleanest way to build messages is f-strings. Put f before the opening quote, then wrap any variable or expression in curly braces. Python fills it in at runtime. You can put any value, calculation, or method call inside the {}.

f-strings evaluate any expression inside {} at runtime and embed the result as a string. A colon after the value introduces a format spec: a compact syntax for controlling decimal places, alignment, and number formatting. They are faster and more readable than concatenation, and they do not require explicit str() calls.

Each {} in an f-string ends up calling format(value, spec), which hands off to the value's own __format__ method (a dunder, the double-underscore method Python calls to format a value). So any class can decide how it looks inside an f-string by defining __format__. The conversion flags !r, !s, and !a run repr(), str(), or ascii() on the value first; !r is the one to remember for debugging, because it shows a value the way you would type it in code (quotes, escapes and all), shown in practice below.

python
name = "Alice"
score = 980

# concatenation: clunky, requires str() for numbers
print("Player: " + name + ", Score: " + str(score))

# f-string: readable, no manual conversion
print(f"Player: {name}, Score: {score}")

You can put any expression inside {}: arithmetic, method calls (like .upper(), covered fully in the Strings chapter), format specs:

python
price = 49.99
tax = 0.2
total = price * (1 + tax)

print(f"Total: {total:.2f}")          # Total: 59.99
print(f"Name: {name.upper()}")        # Name: ALICE
print(f"2 + 2 = {2 + 2}")             # 2 + 2 = 4

The format spec after : controls how the value is displayed:

python
ratio = 0.8765
count = 1234567
label = "revenue"

print(f"{ratio:.1%}")       # 87.6%
print(f"{count:,}")         # 1,234,567
print(f"{label:>12}")       # "     revenue"

:.2f means "two decimal places". You will use it constantly for prices and measurements. Everything else is there when you need it. The main thing: anything can go inside {}, not only variable names.

:.2f and :.0% cover most formatting needs. The alignment specifiers (>, <, ^) with a width produce neat tabular output. The general pattern is {value:[align][width][.precision][type]}. Once you recognise the parts, any spec is readable without memorising all combinations.

The same spec can mean different things to different types, because each type formats itself. f"{'hi':5}" pads text on the right (text aligns left by default) while f"{42:5}" pads on the left (numbers align right): same :5, opposite result. Each built-in type carries its own rules for this format mini-language, and a custom class sets its own by defining __format__.

JunoFormatting output with f-strings Put f before the opening quote and wrap anything in {}: a variable, a sum, even a method call. A colon adds formatting, like :.2f for two decimal places. So much cleaner than gluing strings together with +.
JunoFormatting output with f-stringsf"..." drops any expression's result straight into the string, no str() needed. After a colon comes the format spec: :.2f, :,, :>12 and friends. Cleaner and faster than concatenation.
JunoFormatting output with f-strings Each {} calls the value's own __format__, so the spec after the colon means whatever that type decides. !r is the one to remember: it shows a value the way you'd type it, which makes stray whitespace and other invisible junk stand out when you're debugging.

Getting input from the user

input() pauses your program and waits for the user to type something. Whatever they type (and press Enter on) comes back as the return value. The string in the parentheses is the prompt the user sees.

python
name = input("What's your name? ")
print(f"Hello, {name}!")

input() always returns a string, no matter what the user types. Type 42 and you get back "42", not the number 42. To do arithmetic with it, convert explicitly:

python
age = int(input("How old are you? "))
print(f"In ten years you'll be {age + 10}.")

What if the user types something that cannot be converted? Python raises a ValueError. Handling that properly is covered in the Files and exceptions chapter.

input() writes the prompt to standard output, reads a line from standard input, strips the trailing newline, and returns the result as a string. There is no type inference. Everything from the terminal arrives as text; you declare what type you need by converting explicitly at the boundary.

python
name = input("What's your name? ")
age = int(input("How old are you? "))

This pattern (receive text, convert to the type you need) applies everywhere external data arrives. int(), float(), and str() are the conversion tools. If the string cannot be converted, Python raises ValueError, covered in the Files and exceptions chapter.

input() is a thin wrapper: it writes the prompt to sys.stdout (standard output), flushes it so the prompt shows before the program blocks, reads one line from sys.stdin (standard input), strips the trailing newline, and hands back a str. It is always a str by design, because Python can't guess whether "42" was meant as an integer, a float, or literal text. So you convert at the boundary. This is the fundamental shape of Python I/O: external data arrives as text, and your code decides its type at the entry point.

python
name = input("Enter your name: ")
score = float(input("Enter your score (0.0 to 1.0): "))

print(f"Name:  {name!r}")      # !r reveals any invisible whitespace
print(f"Score: {score:.1%}")
JunoGetting input from the userinput() shows your prompt, waits for a line, and always hands it back as a string, even if they typed a number. Need a number? Convert it yourself with int() or float() as it comes in, then carry on with the right type.
JunoGetting input from the userinput() always returns a str, no type inference. Convert at the boundary with int() or float(), and expect a ValueError if the text doesn't parse. That receive-then-convert pattern shows up everywhere external data does.
JunoGetting input from the user Everything from input() is a str by design, because Python can't guess what you meant. Convert at the entry point and handle the ValueError when it won't parse. Same shape as every other place external data lands.

Writing to stderr

By default, print() writes to standard output: the stream that appears in the terminal and flows into pipes. Python also has standard error, a separate stream for diagnostics and warnings. They look identical in a terminal but are distinct: when you pipe a script's output into another command, only stdout goes through. Stderr always reaches the terminal.

Writing to stderr uses print()'s file argument. That requires importing sys, which is covered in the Modules chapter. For now, knowing the two streams exist and why they are separate is enough.

In practice

A quiz that personalises itself from user input:

python
name = input("What's your name? ")
subject = input("Which subject? ")

print(f"Okay, {name}. Starting your {subject} quiz.")
print("Good luck!")

Both inputs come back as strings and go directly into the f-strings. No conversion needed because you are using them as text, not numbers.

A temperature converter with aligned tabular output:

python
celsius = float(input("Temperature in Celsius: "))
fahrenheit = celsius * 9 / 5 + 32

print(f"{'Celsius':>12} {'Fahrenheit':>12}")
print(f"{celsius:>12.1f} {fahrenheit:>12.1f}")

float() handles both integers and decimals from the user. The >12 spec keeps columns aligned regardless of how many digits the numbers have. Try entering 100 and -40: the output stays tidy either way.

Using !r to surface exactly what Python received, useful when output looks wrong:

python
name = input("Enter your name: ")
score = float(input("Enter your score (0.0 to 1.0): "))

print(f"Name:   {name!r}")
print(f"Score:  {score!r}")
print()
print(f"Result: {name}: {score:.1%}")

{name} and {name!r} display identically when input is clean. The difference appears when there are trailing spaces or other invisible characters. Getting into the habit of !r during debugging makes unexpected values visible immediately.