Lesson 6 of 25

User Input & Output

print() Has More Settings Than You Think

print() accepts any number of values, converts each to text, and writes them out with a single space between them and a newline at the end. Both of those defaults are just keyword arguments you can change. sep controls what goes between the values and end controls what goes after the last one.

Setting end="" is how you build one line out of several print calls — useful for progress dots or for printing a row of numbers. Setting sep is how you produce a date as 2026-08-02 or a CSV row without building the string yourself. Neither is essential, but knowing they exist stops you writing awkward concatenation to work around a default.

One detail worth understanding early: print() converts each argument by calling str() on it, so it never raises a TypeError the way + does. print("Total:", 257) is perfectly happy while print("Total: " + 257) is not. Passing a list prints the list with its brackets and quotes intact, which is exactly right when you are debugging and wrong when you are showing something to a user — that is what join() and f-strings are for.

Example
print("Hello, World!")

# Several values: converted with str(), joined by a space
print("Name:", "Asha", "Marks:", 87)      # Name: Asha Marks: 87

# sep changes what goes between them
print(2026, 8, 2, sep="-")                # 2026-8-2
print("a", "b", "c", sep="")              # abc

# end changes what goes after the last one
print("Loading", end="")
print("...", end="")
print(" done")                            # Loading... done

# * unpacks a list into separate arguments
marks = [87, 92, 78]
print(marks)              # [87, 92, 78]   good for debugging
print(*marks)             # 87 92 78       good for output
print(*marks, sep=", ")   # 87, 92, 78

# print never complains about types
print("Total:", 257)      # fine
# print("Total: " + 257)  # TypeError: can only concatenate str to str
  • sep=" " — what goes between values; default is one space
  • end="\n" — what goes after the last value; default is a newline
  • print(*items) — unpack a list so each element becomes its own argument
  • print(x) uses str(x); it never raises a TypeError for mixed types
  • flush=True — force the text out immediately instead of waiting for a buffer
Notes
  • In Python 3 print is a function and the parentheses are required. Old tutorials showing print "hello" were written for Python 2 and will not run — treat the rest of such an answer with suspicion too.

input() Always Hands You a String

input() stops the program, waits for the user to type a line and press Enter, and returns what was typed with the newline removed. The optional argument is a prompt printed before the cursor; passing it to input() is better than a separate print() because the cursor then sits on the same line as the question.

The one thing to remember is in the section title: the return value is always a string, even when the user types digits. input() has no idea what you intend to do with the value, so it does not guess. If you skip the conversion, age + 1 raises a TypeError, and — worse — marks * 2 quietly gives you "8787" instead of 174, because * repeats strings.

Convert explicitly with int() for whole numbers and float() for decimals. Doing it inline, as age = int(input("Age: ")), is compact and common, but be aware of the trade-off: if the user types something that is not a number, the program crashes with ValueError right there. That is acceptable in a practice script and unacceptable in anything someone else will run, which is what the next section is about.

Example
name = input("What is your name? ")
print(f"Hello, {name}!")

# input() returns a string — always
age_text = input("How old are you? ")
print(type(age_text))      # <class 'str'>  even if you typed 20

# What goes wrong if you forget to convert
marks_text = input("Marks: ")     # user types 87
print(marks_text * 2)             # 8787  — string repetition, not arithmetic
# print(marks_text + 1)           # TypeError: can only concatenate str to str

# Convert explicitly
age = int(age_text)
print(f"Next year you will be {age + 1}")

height = float(input("Height in metres: "))
print(f"That is {height * 100:.0f} cm")

# Inline conversion — compact, but crashes on bad input
# marks = int(input("Marks: "))   # ValueError if the user types 'eighty'
Notes
  • input() strips the newline but keeps any spaces the user typed at either end. If you are going to compare the value against something, apply .strip() immediately: choice = input("Option: ").strip().lower().

Validating What the User Typed

Any program that reads input must decide what to do when the input is wrong, because it will be wrong eventually. There are two workable approaches and one that only looks workable.

The approach that only looks workable is checking with .isdigit() before converting. It is fine for a positive whole number and silently wrong for everything else: "-5".isdigit() is False because of the minus sign, and "3.5".isdigit() is False because of the dot. If you use it, use it knowing it means "consists only of digit characters", not "is a valid number".

The approach that scales is to attempt the conversion and catch the failure with try / except ValueError. Lesson 17 covers exceptions properly; the pattern is worth meeting now because it is how real input handling is written. Wrap it in a while True loop that only breaks once the value is good, and the user gets to try again instead of watching the program die.

Validation is not only about types. A mark of 250 out of 100 converts to an integer perfectly well and is still nonsense, so range checks belong in the same loop as the conversion. The rule of thumb is to validate once, at the boundary where data enters the program, so that everything downstream can assume the value is sane.

Example
# Looks like validation, is not
print("-5".isdigit())      # False — the minus sign is not a digit
print("3.5".isdigit())     # False — the dot is not a digit
print("87".isdigit())      # True

# The pattern that actually works
while True:
    raw = input("Enter marks (0-100): ").strip()
    try:
        marks = int(raw)
    except ValueError:
        print("That is not a whole number. Try again.")
        continue
    if not 0 <= marks <= 100:
        print("Marks must be between 0 and 100.")
        continue
    break

print(f"Recorded: {marks}")

# Menu choices: validate against a fixed set
valid = {"add", "list", "quit"}
choice = input("Command: ").strip().lower()
if choice in valid:
    print(f"Running {choice}")
else:
    print(f"Unknown command. Choose one of: {', '.join(sorted(valid))}")
  • Convert inside try, catch ValueError, and loop until the value is good
  • .isdigit() rejects negatives and decimals — do not treat it as a number check
  • Check the range as well as the type; a valid integer can still be a nonsense value
  • .strip().lower() the moment a text choice arrives, so comparisons are honest
  • Validate once at the boundary, then trust the value everywhere downstream
Notes
  • continue jumps back to the top of the loop and break leaves it. In the example above, every failed check uses continue, so break is reached only when every check has passed.

Reading Several Values from One Line

Coding tests and online judges usually give you several numbers on a single line, and reading them one input() at a time does not work. The idiom is to read the whole line and split() it, then convert each piece.

input().split() with no separator splits on any run of whitespace and drops empty pieces, so extra spaces between the numbers do not matter. From there, [int(x) for x in parts] converts the lot, and map(int, parts) does the same thing in a form you will see constantly in other people's solutions. map returns a lazy object rather than a list, so wrap it in list() if you need to index into it or use it twice.

For a fixed number of values, unpack directly: a, b = input().split() assigns both names in one line. The catch is that this raises ValueError if the user types the wrong number of values — "not enough values to unpack" — so it belongs in code where the input format is guaranteed, such as a judge submission, rather than in an interactive tool.

Example
# One line, several numbers:  87 92 78
line = input("Enter marks separated by spaces: ")
parts = line.split()                 # ['87', '92', '78']
marks = [int(x) for x in parts]      # [87, 92, 78]
print(sum(marks), max(marks))

# The same thing with map()
marks = list(map(int, input().split()))

# Fixed count: unpack straight into names
# input:  5 7
a, b = input().split()
print(int(a) + int(b))               # 12

# Or convert while unpacking
a, b = map(int, input().split())
print(a + b)                         # 12
# ValueError: not enough values to unpack — if only one number was typed

# A different separator: 'Asha,87,92'
name, *scores = input().split(",")
print(name, [int(s) for s in scores])
Notes
  • When a problem gives you thousands of lines, input() becomes the slow part. import sys then data = sys.stdin.read().split() pulls everything in one go and is dramatically faster on large inputs.

Where Output Actually Goes

print() writes to standard output, one of two text streams every program has. The other is standard error. They usually both appear in your terminal, which is why beginners never notice the difference, but they are separate channels and they can be redirected separately. That is the whole point: a script can send its results to a file while its warnings still appear on screen.

Write to standard error with print("...", file=sys.stderr). Use it for anything that is a message about the program rather than a result of it — progress notes, warnings, error explanations. If someone later runs python report.py > out.txt, the report lands in the file and the warnings stay visible, which is exactly the behaviour you want.

The same file= argument accepts an open file object, so print(row, file=f) is a perfectly reasonable way to write lines to a file without learning any new syntax. Lesson 16 covers files properly, including why you should open them with with.

One last practical point: output is buffered. Python collects text and writes it out in chunks, so a progress message printed inside a long loop may not appear when you expect. Passing flush=True forces it out immediately. This is the usual explanation when a script seems to hang silently and then dumps all its output at the very end.

Example
import sys

# Results on stdout, messages on stderr
print("Report for August 2026")                     # stdout
print("warning: 3 rows had no marks", file=sys.stderr)  # stderr

# Now:  python report.py > out.txt
# The report goes into out.txt, the warning still shows on screen.

# file= also accepts an open file
with open("marks.txt", "w") as f:
    for name, mark in [("Asha", 87), ("Ravi", 92)]:
        print(f"{name},{mark}", file=f)

# Buffering: without flush the dots may all appear at the end
import time
for i in range(3):
    print(".", end="", flush=True)
    time.sleep(0.5)
print(" done")
  • sys.stdout — results; this is where plain print() goes
  • sys.stderr — warnings and diagnostics; survives redirection of the results
  • print(x, file=f) — write a line to any open file object
  • flush=True — push the text out now instead of when the buffer fills
Notes
  • Once a program grows past a script, replace diagnostic print() calls with the standard library's logging module. It gives you severity levels and lets you switch the detail on or off without editing every line.

Where input() Misbehaves

input() reads from standard input, and that stream is not always a person at a keyboard. When there is nothing left to read, input() raises EOFError — "end of file". You will meet this the first time you run a script through a pipe, on an online judge with fewer test lines than your loop expects, or in an environment with no console attached.

Notebook environments behave differently again. In Jupyter and Google Colab, input() works but pops up a box, and the cell sits waiting until you answer it, which is easy to miss if you have scrolled away. Some automated graders disable it altogether. If you are writing code that must run unattended, read from command-line arguments via sys.argv or from a file instead of prompting.

Two smaller points round this off. First, do not use input() for passwords — the characters appear on screen. The standard library's getpass module reads them without echoing. Second, if your program will be run from a terminal by other people, accepting values as command-line arguments is usually kinder than an interactive prompt, because it can be scripted and repeated.

Example
import sys

# EOFError when there is nothing left to read
try:
    line = input()
except EOFError:
    print("no more input", file=sys.stderr)
    line = ""

# Command-line arguments instead of prompting
# Run as:  python marks.py Asha 87
#   sys.argv[0] is the script name
if len(sys.argv) >= 3:
    name = sys.argv[1]
    marks = int(sys.argv[2])
    print(f"{name}: {marks}")
else:
    print("usage: python marks.py <name> <marks>", file=sys.stderr)

# Passwords should not be echoed
from getpass import getpass
# secret = getpass("Password: ")   # typed characters stay hidden
Notes
  • sys.argv is a list of strings, and sys.argv[0] is the script's own name — so the first real argument is at index 1. As with input(), every element is text and needs converting before you do arithmetic with it.
Ask AI