Strings, Variables and I/O
Programs have to deal with people — take something in, hand something back. This lecture also introduces functions, your first real weapon against complexity.
By the end of this lecture you should be able to
- Say precisely what an assignment statement does, and explain id() and is with the name-points-at-value model
- Embed values in output with f-strings, controlling decimals and alignment
- Handle what input() gives back, and know what happens when conversion fails
- Define functions that take arguments and return values
- Keep print() and return straight, and stop confusing the two
Contents
Variables
What assignment does
x = 10
print(x)
x = 20 # same name, now bound to a new value
print(x)
Read = as “bind the value on the right to the name on the left.” It is not the equals sign of mathematics. In maths x = x + 1 is a false statement; in a program it is perfectly ordinary and means “take what x is now, add one, bind it back to x.”
count = 5
count = count + 1 # the right side evaluates to 6, then binds to count
print(count)
Naming
A name starts with a letter or underscore, and continues with letters, digits or underscores. Names are case sensitive — age and Age are different variables.
Python’s keywords are off limits (if, for, class, return and the rest):
import keyword
print(keyword.kwlist)
Convention matters more than the rules:
- Use meaningful names.
student_countbeatsn, andnbeatsx - Join words with underscores:
max_score,user_name. This is snake_case, and it is the Python convention - Avoid
l(lowercase L) andO(uppercase o) as standalone names — they look far too much like1and0
What a variable really is
Picture memory as a long strip of cells, each with a number — its address. Data lives in the cells; the program finds it by address.
x = 10 does two things: it puts 10 somewhere in memory, and it makes the name x point at that place.
A variable is not a box. It is a name stuck onto a value. The box metaphor — a container you swap the contents of — survives as long as you only have numbers and strings. It will mislead you badly once lists show up in L5.
The right picture is an arrow: name → value. x = 20 doesn’t replace the 10 inside a box; it re-points the arrow at a different cell.
id() hands you the address of whatever a name currently points at:
x = 10
print(id(x))
y = x # y now points at the same place
print(id(y))
print(x is y) # is asks "the same one?"
After y = x the two ids match, which tells you assignment copies the arrow, not the value: there is one 10 in memory and two names pointing at it.
== and is are different questions
== asks “are the values equal?”; is asks “are these the same object?”:
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # same contents
print(a is b) # but not the same object
(The square brackets make a list, which is L5’s topic. It’s borrowed here because numbers and short strings hide the distinction.)
Same contents, so == is True. But they were built separately and occupy two places in memory, so is is False — two identical photocopies are still two sheets of paper.
A passing acquaintance is enough for now. L5 makes this model do real work — most of that lecture is a direct consequence of it.
Assigning several names at once
Both sides of = can be comma-separated, as long as the counts match:
a, b = 7, 3
print(a, b)
# the right side is evaluated in full first,
# so swapping needs no temporary variable
a, b = b, a
print(a, b)
The order is the point: the whole right-hand side is evaluated, and only then are the names bound. In a, b = b, a, the right side grabs the current b and a (3 and 7) before anything is rebound, and the swap falls out for free.
C and Java need a temporary variable for this. The pattern shows up again and again — catching several return values from a function, and pairing with enumerate in L4’s loops.
Compound assignment
x = 10
x += 3 # same as x = x + 3
print(x)
x -= 5
x *= 2
x //= 3
print(x)
+=, -=, *=, /=, //=, %= and **= all work this way. Shorter, and the name appears only once — so when you rename it, there is nothing to miss.
Comments
Everything after a # to the end of the line is ignored by Python and written for humans.
# this whole line is a comment
x = 3.14 # and this part is too
print(x)
Good comments explain why, rather than restating what the code plainly does:
# poor: add one to x
x += 1
# better: skip the header row
x += 1
Strings
Three kinds of quotes
a = 'single'
b = "double"
c = """triple quotes
span
multiple lines"""
print(a, b)
print(c)
Single and double quotes are entirely equivalent. One practical difference: whichever kind appears inside your text, use the other one outside, and you avoid escaping altogether.
print("He said 'hello'")
print('She said "hello"')
Joining and repeating
first = "Shanghai"
second = "Jiao Tong"
print(first + " " + second) # + joins
print("-" * 30) # * repeats
print(first * 2)
Length, indexing and comparison
s = "Python"
print(len(s)) # length
print(s[0]) # first character; indexing starts at 0
print(s[-1]) # last
print(s[2:5]) # a slice: includes the start, stops before the end
Strings compare lexicographically, character by character, using their code points:
print("apple" < "banana") # 'a' < 'b'
print("Zebra" < "apple") # uppercase code points are smaller
print("abc" < "abd") # first two match, decide on the third
print("abc" < "abcd") # a prefix sorts first
Strings are immutable — you cannot change a character in place:
s = "hello"
s = s.upper() # this builds a new string and rebinds s
print(s)
s = "hello"
s[0] = "H" # this raises an error
String methods get proper treatment in L6. This is enough for now.
Converting between types
type() tells you what something is:
print(type(42))
print(type(3.14))
print(type("42"))
print(type(True))
int(), float() and str() convert:
print(int("42") + 1) # string → integer
print(float("3.14") * 2) # string → float
print(str(42) + "!") # number → string
print(int(3.99)) # float → integer: truncates, does not round
print(int(-3.99)) # negatives truncate toward zero as well
Conversion raises an exception when it cannot succeed:
print(int("abc"))
print(int("3.14")) # this fails too — see if you can say why
int() accepts only a string that spells an integer. "3.14" has to go through float() first.
Input
input() always returns a string
# clicking Run opens a prompt box — try typing a number
age = input("How old are you? ")
print("you entered:", age)
print("its type is:", type(age))
Whatever you type, input() hands back a string. Type 18 and you get "18", not 18.
This is the single most common mistake of the first month:
n = input("Enter a number: ")
print(n * 3) # you wanted triple; you got it repeated three times
Convert first:
n = int(input("Enter a number: "))
print(n * 3)
eval(): useful, and worth being careful with
eval() evaluates a string as a Python expression:
print(eval("1 + 2 * 3"))
print(eval("2 ** 10"))
Convenient — eval(input()) reads a number or a whole expression in one go.
Formatted output
Why it exists
You want to print “Alex scored 87.5” where the name and the number come from variables. Concatenation makes a mess of it:
name = "Alex"
score = 87.5
print(name + " scored " + str(score)) # works, but it is noisy
f-strings — what this course uses
Put an f before the quote and write the variables straight into {}:
name = "Alex"
score = 87.5
print(f"{name} scored {score}")
Any expression can go inside the braces:
a, b = 7, 3
print(f"{a} + {b} = {a + b}")
print(f"{a} / {b} = {a / b}")
print(f"mean: {(87 + 92 + 65) / 3}")
Controlling the format
{value:spec} gives you decimals, width, alignment and base:
pi = 3.14159265
print(f"{pi:.2f}") # two decimal places
print(f"{pi:.4f}")
print(f"{pi:10.2f}") # total width 10, right aligned
print(f"{pi:<10.2f}|") # left aligned
print(f"{pi:^10.2f}|") # centered
n = 255
print(f"{n:b}") # binary
print(f"{n:o}") # octal
print(f"{n:x}") # lowercase hex
print(f"{n:X}") # uppercase hex
m = 5
print(f"{m:b}") # 101
print(f"{m:08b}") # zero-padded to 8 digits — handy for lining things up
# a small aligned table
data = [("Alex", 87.5), ("Blake", 92.0), ("Chen", 65.25)]
for name, score in data:
print(f"{name:<8}{score:>8.1f}")
One form that is excellent while debugging — {expr=} prints the expression and its value:
x = 42
y = [1, 2, 3]
print(f"{x=}")
print(f"{y=}")
Two older styles you will meet in other people’s code
Python has had three generations of formatting syntax. This course uses f-strings throughout, but you will run into the earlier two:
name, score = "Alex", 87.5
# first generation: C-style %
print("%s scored %.1f" % (name, score))
# second generation: str.format()
print("{} scored {:.1f}".format(name, score))
# third generation: f-string — use this
print(f"{name} scored {score:.1f}")
Same output all three times. What f-strings buy you is that each variable sits where it appears, so you never count placeholders against a list of arguments at the end.
print’s sep and end
print("a", "b", "c") # a space between them by default
print("a", "b", "c", sep="") # nothing between them
print("a", "b", "c", sep=" | ") # your own separator
print("2026", "09", "14", sep="-")
print("no newline", end="")
print(" ← continued")
Functions
Why they exist
Imagine a program a few thousand lines long, written straight down one file.
- Changing anything means guessing what else it might affect
- The same logic gets copy-pasted in ten places, and edits miss some of them
- Nobody else — including you in three months — can follow it
A function packages a piece of logic under a name, so you can invoke it by that name later. It is the most basic tool programming has for fighting complexity: cut a big problem into small ones and think about only one at a time.
You have been calling other people’s functions all along: print(), len(), int(), input(). Now write your own.
Three parts: name, parameters, return value
def area_of_circle(r): # the def keyword, a name, a parameter
return 3.14159 * r * r # the return value
print(area_of_circle(1))
print(area_of_circle(2.5))
defintroduces a definitionarea_of_circleis the name; the same rules apply as for variablesris a parameter — the function’s input- what follows
returnis the return value — the function’s output - the body is indented
Indentation decides what belongs to the function
Python marks blocks by indentation. Consecutive lines at the same indentation form one block.
def greet(name):
print("about to say hello") # indented — inside the function
print(f"hello, {name}") # indented — inside the function
print("not indented, so outside") # outside
greet("Alex")
The convention is four spaces per level. Never mix tabs and spaces — they look identical and Python will reject the file. VS Code converts tabs to spaces by default, so this rarely bites.
Defining is not running
def say_hi():
print("hi")
print("program starts")
# the function is defined above but has not run once
say_hi() # now it runs
say_hi() # and it can run as often as you like
A definition only registers the code. It does not execute it. Only a call — the name followed by parentheses — runs the body.
And the definition has to come first:
say_bye()
def say_bye():
print("bye")
Passing arguments
The values in the parentheses at the call site are arguments, bound in order to the parameters from the definition:
def rectangle_area(width, height):
return width * height
print(rectangle_area(3, 4)) # width=3, height=4
print(rectangle_area(4, 3)) # different order — same answer here, but not in general
You can also name them, in which case order stops mattering:
def introduce(name, age, city):
print(f"{name}, {age}, from {city}")
introduce("Alex", 18, "Shanghai")
introduce(age=18, city="Shanghai", name="Alex") # named arguments
Inside and outside are separate
Variables created inside a function are local. They vanish when the function ends, and the outside world never sees them:
def compute():
result = 42 # local
return result
print(compute())
print(result) # error: there is no result out here
That separation is a feature. It means you can write a function without worrying about clashing with names used elsewhere:
def f():
x = "the x inside"
return x
x = "the x outside"
print(f())
print(x) # the function did not touch it
The full scoping rules (LEGB) are L14’s subject. “Inside and outside do not interfere” is enough for now.
return
return does two things: hands a value back to the caller, and ends the function immediately.
def check(n):
if n > 0:
return "positive"
return "not positive" # only reached if the return above did not fire
print(check(5))
print(check(-3))
Nothing after a return executes:
def demo():
print("this runs")
return 1
print("this never runs")
print(demo())
You can return several values at once — they come back packed in a tuple:
def min_max(a, b, c):
return min(a, b, c), max(a, b, c)
low, high = min_max(3, 9, 5)
print(low, high)
A function with no return gives back None:
def no_return():
print("I only print; I return nothing")
result = no_return()
print("returned:", result)
print("type: ", type(result))
print() versus return
This is the most common mistake beginners make, and the hardest for them to spot on their own. Read this section properly.
Both appear to “produce the answer,” but they do entirely different jobs:
print()is for a human — it puts characters on the screen. Once shown, it is gone; the program cannot get at itreturnis for the program — it hands a value back to the caller, which can go on computing with it
Two near-identical functions:
def add_print(a, b):
print(a + b) # only displays
def add_return(a, b):
return a + b # hands the value back
add_print(2, 3) # 5 appears on screen
add_return(2, 3) # nothing appears at all
The second one computed the right answer, and you see nothing — because it handed the value back and nobody caught it.
The difference is whether you can carry on:
def add_print(a, b):
print(a + b)
def add_return(a, b):
return a + b
x = add_print(2, 3) # 5 is displayed, but x is None
y = add_return(2, 3) # nothing is displayed, but y is 5
print("x =", x)
print("y =", y)
def add_print(a, b):
print(a + b)
# try to use the results and it falls apart completely
total = add_print(2, 3) + add_print(4, 5)
None + None is a TypeError.
There is a concrete consequence too: the lab platform marks your work by calling your function and comparing the value it returns. A function that prints instead of returning hands back None and scores zero — even when the algorithm is perfect and the screen output looks exactly right.
Using functions other people wrote
Python ships with a large collection of ready-made functions, grouped into modules. Bring one in with import:
import math
print(math.sqrt(16)) # square root
print(math.pi)
print(math.floor(3.7)) # round down
print(math.ceil(3.2)) # round up
print(math.factorial(5))
With math.pi available, that circle function gets more accurate:
import math
def area_of_circle(r):
return math.pi * r ** 2
print(f"{area_of_circle(1):.6f}")
How modules really work — where Python looks for them, how to install more, how to write your own — is L10. For now, import name followed by name.function() is all you need.
Summary
=binds; it is not mathematical equality. A variable is a name pointing at a value, not a boxid()gives the address,isasks whether two names are the same object. Use==for values;isonly againstNonea, b = b, aevaluates the right side in full first, so swapping needs no temporary- Give variables meaningful snake_case names, and never shadow
list,str,sumand friends - Strings compare lexicographically, uppercase before lowercase, and they are immutable
int()truncates;round()rounds, using banker’s rounding on exact halvesinput()always returns a string — convert it yourselfeval()is dangerous; useint()orfloat()when you want a number- Use f-strings for all formatting.
{v:.2f}for decimals,{v:>10}for alignment - A function is a name, parameters and a return value. Defining is not running, and definitions come first
- Locals do not escape the function
printis for people,returnis for the program. Inside a function,return
Exercises
- Write
celsius_to_fahrenheit(c)usingF = C * 9/5 + 32, returning the result to one decimal place. Note that it mustreturn, notprint. - Read two integers and print their sum, difference, product and quotient (two decimal places). Use f-strings, formatted as
12 + 5 = 17. - What is wrong with this? Fix it:
def double(n): print(n * 2) result = double(21) print(result + 1) - Write
initials(full_name)so that"Zhang San Feng"gives"Z.S.F.". (Hint:.split()breaks a string apart on spaces.) - Use f-strings to print the first three rows of a multiplication table, each product right-aligned in a field four characters wide.
- Explain why these two produce different results:
x = 10 def f(): x = 20 f() print(x) # what does this print? x = 10 def g(): return 20 x = g() print(x) # and this?
The lab problems for this lecture are in Lab 3.