Three built-ins carry most of your first programs: print() to talk to the world, input() to listen to the user, and the assignment operator that creates a variable. Each of them has a behaviour that trips up beginners in a specific, predictable way. Get these three right and the rest of Python's syntax starts to look like what you already expected.
What does print() really do?
print() takes any number of objects, converts each one to text, joins them with a separator, and writes the result to standard output followed by a newline. The defaults are a single space between items and a newline at the end, and both can be overridden.
print('alpha', 'beta', 'gamma') # space separated, newline at the end
print('alpha', 'beta', 'gamma', sep=' | ') # custom separator
print('waiting...', end='') # no newline yet
print('done') # continues the same line
print() # just a newline
print('None prints as:', None)
print('print itself returns:', print('this inner call runs first'))
Two details in that output are worth pausing on. The last line shows that print() returns None, so you cannot write message = print('hi') and expect to capture text; you are capturing the absence of a value. And print will happily print None, which is how you notice that a function forgot to return anything.
Two keyword arguments earn their keep. end lets you build output one fragment at a time without a newline after each, which is how progress indicators are written. flush forces the text out immediately rather than leaving it in a buffer, which matters when the output is a log file or a live dashboard and someone is waiting to see it.
Why does input() always hand you a string?
Because a keyboard produces characters, not numbers. input() reads one line from the user, removes the trailing newline, and returns that line as a str. It does not guess whether you meant the text '30' or the number 30.
def ask(prompt, default=''):
"""Read one line from the user; fall back to a default when there is no console."""
try:
return input(prompt)
except EOFError:
return default
age = ask('Age: ', '30')
print('the value you typed is:', age)
print('its type is:', type(age).__name__)
try:
print(age + 1)
except TypeError as error:
print('adding 1 failed with:', error)
print('after conversion:', int(age) + 1)
That TypeError is the single most common error in a beginner's first interactive program, and the message Python gives you is unusually helpful: it tells you that you cannot add a str to an int. The fix is int(age), and the deeper lesson is that conversion is your job, because Python will not silently guess what a human meant.
One small but useful detail: input() keeps leading and trailing spaces inside the line, so input().strip() is the standard first move when a value might have been padded. Later in the course you will meet a safer pattern that converts and validates, so a stray letter produces a friendly message instead of a traceback.
There is a second subtlety worth knowing. input() reads a whole line, so if the user types 42 and then presses Enter, you get the string '42'. If the user types nothing at all and presses Enter, you get the empty string, which is falsy. That is why code like if not value: is the standard way to catch an empty answer before trying to convert it.
What is a variable, really?
A Python variable is not a box with a fixed shape. It is a name bound to an object. The object holds the data; the name is a label you can move to a different object at any time. That single idea explains most of Python's surprising behaviour.
first = [1, 2, 3]
second = first
print('same object?', first is second)
second.append(4)
print('first ->', first)
print('second ->', second)
third = first.copy()
third.append(5)
print('after copying, first ->', first)
print('after copying, third ->', third)
print('id of first:', id(first))
first = 'a completely different object'
print('id after rebinding:', id(first), '->', first)
Two names pointing at one list means the list has two references, so appending through either name changes what both names see. The copy() call breaks that link. And the final two lines show the other half of the story: reassigning first did not empty the old list, it simply attached the name to a different object, and id() proves the object changed.
Two consequences follow. Passing a list to a function and mutating it inside changes the caller's list, because you passed a reference. Assigning a list to a second name does not copy it, so second = first and second = first.copy() are very different lines of code. If you are unsure, ask whether you want the same object or a separate one.
How should you name things so other people can read them?
Names are the cheapest documentation in any language, and Python's conventions make them easy to get right.
- Use snake_case for variables and functions:
total_price,read_file. This is the official style guide, and it matters because other Python developers read your code. - Use UPPER_SNAKE_CASE for constants that should not change:
MAX_RETRIES = 3. The convention only works if you respect it. - Never shadow a built-in.
list = [1, 2]breakslist()for the rest of the module, and the bug surfaces far from its cause. - A single leading underscore marks something internal:
_helper. Two leading underscores trigger name mangling inside classes. - Names are case-sensitive, so
totalandTotalare two different variables. That is a bug waiting to happen.
Python also lets you attach a type hint to a name or a parameter. Hints are optional and Python never enforces them at runtime, but they let editors and type checkers catch mistakes before you run the code.
import sys
MAX_RETRIES = 3
def greet(name: str, excited: bool = False) -> str:
suffix = '!' if excited else '.'
return f'Hello, {name}{suffix}'
print(greet('Ada'))
print(greet('Ada', excited=True))
if (remaining := sys.maxsize) > 100:
print('the walrus assigned inside the condition:', remaining)
count = 0
while (count := count + 1) < 3:
print('count is now', count)
The walrus operator := assigns a value as part of an expression, which saves a separate line when you need a value and a condition that depends on it. It is worth learning once you are comfortable with ordinary assignment, and worth avoiding in code other people have to maintain.
Your turn: build a tip calculator
- Create a file called
tips.pythat asks for a bill amount and a tip percentage usinginput(), converts both withfloat(), and prints the total including tip. - Run it and type a bill of
42.50and a tip of12. Your total should be 47.60. If it says "unsupported operand", you forgot to convert one of the two inputs. - Add a line that prints the type of each converted value with
type(value).__name__, and one that prints the type of the raw input, so you can see the change happen. - Now make it robust: wrap the conversion in a
tryblock, catchValueError, and print "Please type a number" instead of crashing when someone types letters. - Finally, rename your variables to
bill_amountandtip_percent, and add atip_totalconstant-style name for the tip itself. Read the file top to bottom and ask whether a stranger could guess each variable's role from its name alone.
Recap
print()joins its arguments with a space and ends with a newline; usesepandendto change both.print()returnsNone, so never assign its result expecting text.input()always returns a string, and converting it is your responsibility.- A variable is a name bound to an object. Rebinding moves the name; mutating changes the object, and every other name pointing at it sees the change.
- Use snake_case, UPPER_SNAKE_CASE for constants, and never shadow a built-in.