Every meaningful program needs a way to communicate with its users. User input is the bridge between your program and the person running it — it transforms a static script into an interactive experience. Whether you are building a command-line tool, a data processing pipeline, or a quick utility script, understanding how to collect, validate, and safely handle user input is a foundational skill that separates production-ready code from toy examples.
In this tutorial, we will explore every major technique Python offers for gathering user input: from the built-in input() function all the way to command-line argument parsing, stdin piping, and secure password prompts. Along the way, we will build real programs and discuss the security pitfalls that trip up even experienced developers.
The input() function is Python’s primary tool for reading interactive input from the console. When called, it displays an optional prompt string, pauses execution, and waits for the user to type something and press Enter. The value is always returned as a string, regardless of what the user types.
Syntax
variable = input(prompt_message)
The prompt_message is optional but strongly recommended. A clear prompt tells users exactly what you expect from them, which reduces errors and frustration.
Example
name = input("What is your name: ")
print(f"Hello, {name}! Welcome to the tutorial.")
Output
What is your name: Folau Hello, Folau! Welcome to the tutorial.
Notice we use an f-string for formatting. This is the modern, preferred approach in Python 3.6+ because it is more readable and less error-prone than string concatenation or .format().
Since input() always returns a string, you must explicitly convert the value when you need a number, a float, or another type. This is one of the most common sources of bugs for beginners.
Converting to int
age = int(input("Enter your age: "))
print(f"In 10 years, you will be {age + 10} years old.")
Converting to float
price = float(input("Enter the item price: $"))
tax = price * 0.08
total = price + tax
print(f"Price: ${price:.2f}, Tax: ${tax:.2f}, Total: ${total:.2f}")
Converting to bool
There is no direct bool(input()) pattern that works intuitively, because bool("False") returns True (any non-empty string is truthy). Instead, compare against expected values explicitly:
response = input("Continue? (yes/no): ").strip().lower()
should_continue = response in ("yes", "y")
print(f"Continuing: {should_continue}")
Never trust user input. Users will type unexpected things — empty strings, letters when you expect numbers, negative values when you need positive ones. Validating input before using it is a hallmark of professional code.
Checking for empty input
username = input("Enter your username: ").strip()
if not username:
print("Error: Username cannot be empty.")
else:
print(f"Welcome, {username}!")
Checking numeric ranges
age = int(input("Enter your age: "))
if age < 0 or age > 150:
print("Error: Please enter a realistic age.")
else:
print(f"Your age is {age}.")
Checking string patterns
email = input("Enter your email: ").strip()
if "@" not in email or "." not in email:
print("Error: That does not look like a valid email address.")
else:
print(f"Email saved: {email}")
When converting user input to a number, the program will crash with a ValueError if the user types something that cannot be converted. The try/except block lets you handle this gracefully instead of letting your program blow up.
try:
number = int(input("Enter a whole number: "))
print(f"You entered: {number}")
except ValueError:
print("Error: That is not a valid integer.")
Input validation loop — keep asking until valid
In practice, you almost always want to combine try/except with a loop so the user gets another chance:
def get_positive_integer(prompt):
"""Keep asking until the user provides a valid positive integer."""
while True:
try:
value = int(input(prompt))
if value <= 0:
print("Error: Please enter a positive number.")
continue
return value
except ValueError:
print("Error: Invalid input. Please enter a whole number.")
# Usage
quantity = get_positive_integer("How many items? ")
print(f"You ordered {quantity} items.")
This pattern is extremely common in production code. Extract it into a reusable function so you do not repeat yourself.
Sometimes you need multiple values in a single line. The split() method breaks a string into a list of substrings, which you can then unpack or process.
Space-separated values
first, last = input("Enter your first and last name: ").split()
print(f"First: {first}, Last: {last}")
Comma-separated values
values = input("Enter numbers separated by commas: ").split(",")
numbers = [int(v.strip()) for v in values]
print(f"Sum: {sum(numbers)}, Average: {sum(numbers) / len(numbers):.2f}")
Using map() for cleaner conversion
x, y, z = map(int, input("Enter three integers (space-separated): ").split())
print(f"x={x}, y={y}, z={z}, sum={x + y + z}")
For scripts that run in automated pipelines or are invoked from the terminal, command-line arguments are often a better choice than interactive prompts. Python provides two main approaches.
sys.argv is a list of strings passed to your script from the command line. The first element (sys.argv[0]) is the script name itself.
import sys
if len(sys.argv) != 3:
print("Usage: python greet.py <first_name> <last_name>")
sys.exit(1)
first_name = sys.argv[1]
last_name = sys.argv[2]
print(f"Hello, {first_name} {last_name}!")
Run it from the terminal:
# Terminal $ python greet.py Folau Kaveinga Hello, Folau Kaveinga!
For anything beyond trivial scripts, argparse is the standard library module for building proper command-line interfaces. It handles argument parsing, type conversion, help text, and error messages automatically.
import argparse
parser = argparse.ArgumentParser(description="A simple greeting tool.")
parser.add_argument("name", help="The name to greet")
parser.add_argument("-c", "--count", type=int, default=1,
help="Number of times to greet (default: 1)")
parser.add_argument("-u", "--uppercase", action="store_true",
help="Print greeting in uppercase")
args = parser.parse_args()
greeting = f"Hello, {args.name}!"
if args.uppercase:
greeting = greeting.upper()
for _ in range(args.count):
print(greeting)
Run it from the terminal:
# Terminal $ python greet.py Folau --count 3 --uppercase HELLO, FOLAU! HELLO, FOLAU! HELLO, FOLAU! $ python greet.py --help usage: greet.py [-h] [-c COUNT] [-u] name ...
argparse automatically generates --help output and provides clear error messages when users pass invalid arguments. Use it whenever you are building a tool that others (or your future self) will use.
Unix-style piping lets you chain programs together. Python can read from sys.stdin to accept piped input, making your scripts composable with other tools.
import sys
line_count = 0
word_count = 0
for line in sys.stdin:
line_count += 1
word_count += len(line.split())
print(f"Lines: {line_count}, Words: {word_count}")
Usage from the terminal:
# Terminal $ cat myfile.txt | python word_counter.py Lines: 42, Words: 318 $ echo "hello world" | python word_counter.py Lines: 1, Words: 2
A useful pattern is to support both piped input and interactive input:
import sys
if not sys.stdin.isatty():
# Input is being piped
data = sys.stdin.read()
else:
# Interactive mode
data = input("Enter your text: ")
print(f"You provided {len(data)} characters.")
When asking for passwords or other sensitive data, you should never echo the characters back to the screen. The getpass module handles this for you.
import getpass
username = input("Username: ")
password = getpass.getpass("Password: ")
# The password is NOT displayed as the user types
if username == "admin" and password == "secret123":
print("Access granted.")
else:
print("Access denied.")
Note: getpass may not work correctly in some IDEs or notebook environments. It is designed for terminal usage.
def calculator():
"""A simple calculator that takes two numbers and an operator."""
print("=== Simple Calculator ===")
try:
num1 = float(input("Enter first number: "))
operator = input("Enter operator (+, -, *, /): ").strip()
num2 = float(input("Enter second number: "))
except ValueError:
print("Error: Please enter valid numbers.")
return
if operator == "+":
result = num1 + num2
elif operator == "-":
result = num1 - num2
elif operator == "*":
result = num1 * num2
elif operator == "/":
if num2 == 0:
print("Error: Division by zero is not allowed.")
return
result = num1 / num2
else:
print(f"Error: Unknown operator.")
return
print(f"{num1} {operator} {num2} = {result}")
calculator()
def todo_app():
"""A menu-driven to-do list that loops until the user quits."""
tasks = []
while True:
print("\n=== To-Do List ===")
print("1. View tasks")
print("2. Add task")
print("3. Remove task")
print("4. Quit")
choice = input("Choose an option (1-4): ").strip()
if choice == "1":
if not tasks:
print("No tasks yet.")
else:
for i, task in enumerate(tasks, 1):
print(f" {i}. {task}")
elif choice == "2":
task = input("Enter task description: ").strip()
if task:
tasks.append(task)
print(f"Added: {task}")
else:
print("Error: Task cannot be empty.")
elif choice == "3":
if not tasks:
print("No tasks to remove.")
continue
try:
index = int(input(f"Enter task number to remove (1-{len(tasks)}): "))
if 1 <= index <= len(tasks):
removed = tasks.pop(index - 1)
print(f"Removed: {removed}")
else:
print("Error: Invalid task number.")
except ValueError:
print("Error: Please enter a valid number.")
elif choice == "4":
print("Goodbye!")
break
else:
print("Error: Please choose 1, 2, 3, or 4.")
todo_app()
def quiz_game():
"""A trivia quiz that scores the answers."""
questions = [
{
"question": "What keyword defines a function in Python?",
"options": ["a) func", "b) def", "c) function", "d) define"],
"answer": "b"
},
{
"question": "Which data type is immutable?",
"options": ["a) list", "b) dict", "c) tuple", "d) set"],
"answer": "c"
},
{
"question": "What does len() return for an empty list?",
"options": ["a) None", "b) -1", "c) 0", "d) Error"],
"answer": "c"
},
{
"question": "Which module is used for regular expressions?",
"options": ["a) regex", "b) re", "c) regexp", "d) match"],
"answer": "b"
}
]
score = 0
total = len(questions)
print("=== Python Trivia Quiz ===\n")
for i, q in enumerate(questions, 1):
print(f"Question {i}: {q['question']}")
for option in q["options"]:
print(f" {option}")
answer = input("Your answer (a/b/c/d): ").strip().lower()
if answer == q["answer"]:
print("Correct!\n")
score += 1
else:
print(f"Wrong. The correct answer was '{q['answer']}'.\n")
percentage = (score / total) * 100
print(f"Final Score: {score}/{total} ({percentage:.0f}%)")
if percentage == 100:
print("Perfect score! Excellent work.")
elif percentage >= 75:
print("Great job! You know your Python well.")
elif percentage >= 50:
print("Not bad. Keep studying!")
else:
print("Keep practicing. You will get there.")
quiz_game()
# file_stats.py - A command-line tool that reports statistics about a text file.
# Usage: python file_stats.py myfile.txt --verbose
import argparse
import os
def get_file_stats(filepath, verbose=False):
"""Read a file and return its statistics."""
if not os.path.exists(filepath):
print(f"Error: File not found.")
return
with open(filepath, "r") as f:
content = f.read()
lines = content.split("\n")
words = content.split()
characters = len(content)
print(f"File: {filepath}")
print(f" Lines: {len(lines)}")
print(f" Words: {len(words)}")
print(f" Characters: {characters}")
if verbose:
unique_words = set(word.lower().strip(".,!?;:") for word in words)
print(f" Unique words: {len(unique_words)}")
avg_line_len = characters / len(lines) if lines else 0
print(f" Avg line length: {avg_line_len:.1f} characters")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Display statistics about a text file.")
parser.add_argument("file", help="Path to the text file")
parser.add_argument("-v", "--verbose", action="store_true",
help="Show additional statistics")
args = parser.parse_args()
get_file_stats(args.file, args.verbose)
User input is one of the most common attack vectors in software. Treating all input as potentially malicious is not paranoia — it is good engineering.
Never use eval() on user input
This is arguably the most dangerous mistake in Python. eval() executes arbitrary Python expressions, which means a malicious user can run any code on your system:
# DANGEROUS - Never do this
user_input = input("Enter a math expression: ")
result = eval(user_input) # User could type: __import__('os').system('rm -rf /')
# SAFE alternative - use the ast module for math expressions
import ast
def safe_eval_math(expression):
"""Safely evaluate a simple math expression."""
try:
tree = ast.parse(expression, mode='eval')
# Only allow numbers and basic math operators
for node in ast.walk(tree):
if not isinstance(node, (ast.Expression, ast.BinOp, ast.UnaryOp,
ast.Constant, ast.Add, ast.Sub,
ast.Mult, ast.Div, ast.Pow,
ast.USub)):
raise ValueError("Unsupported operation")
return eval(compile(tree, "<string>", "eval"))
except (ValueError, SyntaxError):
return None
result = safe_eval_math(input("Enter a math expression: "))
if result is not None:
print(f"Result: {result}")
else:
print("Error: Invalid expression.")
Sanitize input before using it in file paths, SQL queries, or shell commands
# DANGEROUS - path traversal attack
filename = input("Enter filename: ")
# User types: ../../etc/passwd
# SAFE - validate and sanitize
import os
filename = input("Enter filename: ").strip()
# Remove any path separators
safe_name = os.path.basename(filename)
# Verify it is within the expected directory
filepath = os.path.join("/data", safe_name)
filepath = os.path.realpath(filepath)
if not filepath.startswith("/data/"):
print("Error: Access denied.")
else:
with open(filepath, "r") as f:
print(f.read())
Never pass user input directly to os.system() or subprocess with shell=True
import subprocess
import os
# DANGEROUS
user_dir = input("Directory to list: ")
os.system(f"ls {user_dir}") # User types: /tmp; rm -rf /
# SAFE - use subprocess with a list of arguments (no shell interpretation)
user_dir = input("Directory to list: ")
subprocess.run(["ls", user_dir], check=True)
These are mistakes that developers encounter repeatedly when dealing with user input.
1. Forgetting type conversion
# Bug: string concatenation instead of addition
age = input("Enter your age: ") # Returns "25" (a string)
print(age + 10) # TypeError: can only concatenate str to str
# Fix: convert to int
age = int(input("Enter your age: "))
print(age + 10) # 35
2. Not handling empty input
# Bug: crashes on empty input
number = int(input("Enter a number: ")) # User just presses Enter
# ValueError: invalid literal for int() with base 10: ''
# Fix: check before converting
raw = input("Enter a number: ").strip()
if raw:
number = int(raw)
else:
print("No input provided.")
3. EOFError when input is piped or exhausted
# Bug: crashes when stdin is closed (e.g., in automated testing)
name = input("Name: ") # EOFError if no stdin available
# Fix: handle EOFError
try:
name = input("Name: ")
except EOFError:
name = "default"
print("No input stream available, using default.")
4. Not stripping whitespace
# Bug: trailing spaces cause comparison to fail
choice = input("Type yes to continue: ")
if choice == "yes": # Fails if user typed "yes " with a trailing space
print("Continuing...")
# Fix: always strip
choice = input("Type yes to continue: ").strip().lower()
if choice == "yes":
print("Continuing...")
5. Assuming input encoding
# In Python 3, input() returns a Unicode string by default. # However, if reading from a file or pipe, encoding issues can arise. import sys # Set explicit encoding when needed sys.stdin.reconfigure(encoding='utf-8')
After years of writing Python professionally, these are the habits that pay off consistently:
input(": "), write input("Enter your age (1-120): "). Tell users exactly what you expect.get_valid_int() function once and reuse it..strip() on virtually every input() call to avoid invisible whitespace bugs..lower() or .upper() before comparing text input so “Yes”, “YES”, and “yes” all work.signal or threading to set input timeouts so your program does not hang indefinitely.input() always returns a string. Convert explicitly with int(), float(), or other type constructors when you need a different type.split() and map() to collect multiple values from a single line of input.