Python from zero: values, names, and the two containers
Your first real Python, built around a momo shop: values and types, names as tags (not boxes), if/elif/else, lists with the for loop, dicts as menus, and functions. Three simulators let you watch names re-point at values, a for loop run one item at a time, and a function call happen frame by frame.
mindmap — quick refresh
Prerequisites: Your toolbox — Python 3 installed, VS Code open, and you can run a .py file from the terminal. Time: ~90 minutes if you type everything (do).
Three hard facts before your first program. First, Python is small: the whole language has about 35 keywords, and you'll use maybe 15 of them in this guide — if, for, def, return do most of the work you'll ever need. Second, everything is a value with a type — 150 is an int, "momo" is a str, and the type decides what you're allowed to do with it. Third, every program is the same three moves: values flow into names, decisions pick a path, and repetition runs a block once per item. Functions just bundle those moves so you can reuse them.
We'll learn all of it by building one thing: the ordering side of a small momo shop — a menu, an order, a receipt. Type every snippet into a file and run it; don't paste. Your fingers are learning the syntax while your head learns the ideas.
Values and types
Open a new file values.py, type this, and run python3 values.py in the terminal:
print(150) # an int — whole number
print(9.99) # a float — has a decimal point
print("steam momo") # a str — text, in quotes
print(True) # a bool — True or False, capitalized
print(None) # None — "nothing here", its own type150
9.99
steam momo
True
None
Those five types carry you through this entire guide. When you're not sure what something is, ask — type() is a built-in function that answers:
print(type(150))
print(type("steam momo"))
print(type(9.99))<class 'int'>
<class 'str'>
<class 'float'>
Operators combine values — and the type decides what each operator means:
print(150 + 180) # 330 — addition
print(150 * 3) # 450 — multiplication
print(590 / 4) # 147.5 — / ALWAYS gives a float
print(590 // 4) # 147 — // chops the remainder off
print(590 % 4) # 2 — % gives only the remainder
print("momo" + "!") # momo! — + on strings glues them
Mix the types wrongly and Python refuses, loudly:
print("Rs " + 150)TypeError: can only concatenate str (not "int") to str
The fix — and a tool you will use in every single lab from here on — is the f-string: put an f before the opening quote, and anything inside {...} braces is evaluated and dropped into the text:
name = "steam momo"
price = 150
print(f"{name} costs Rs {price}")
print(f"three plates cost Rs {price * 3}")steam momo costs Rs 150
three plates cost Rs 450
That's string formatting solved for the rest of your Python life. Learn it now, use it always.
Names are tags, not boxes
You just wrote price = 150 — a variable. Here is the one mental model to get right on day one, because it explains half of Python's behavior later: a name is a name tag tied to a value, not a box containing it. price = 150 means "build the int object 150, then tie the tag price to it". Assignment moves the tag, never the value.
Watch what that predicts:
price = 150
total = price # total tags the SAME value price tags
price = 200 # price re-points to a new value...
print(total) # ...so what does total say?150
total still says 150. total = price didn't create a link between the two names — it just tied a second tag to the value price happened to tag at that moment. When price moved on, total stayed. Names don't watch each other.
Step through it — names on the left, values on the right, arrows showing who tags what:
If the arrows model feels like overkill for numbers, hold on to it anyway — the moment values get bigger (lists, dicts, objects), "two tags on one value" stops being a curiosity and starts being the explanation for real surprises. You'll meet one in the lists section below.
Making decisions
Programs choose. In Python, choosing is if / elif / else — Python checks each condition top to bottom, runs the first block whose condition is True, and skips the rest:
total = 410
if total >= 1000:
fee = 0
elif total >= 500:
fee = 50
else:
fee = 100
print(f"delivery fee: Rs {fee}")delivery fee: Rs 100
Conditions are built from comparisons, and each one is itself a value — a bool:
print(150 == 150) # True — == asks "equal?"
print(150 < 60) # False
print("chai" != "momo") # True — != asks "different?"
Careful with the two equals signs: = binds a name, == asks a question. Typing if total = 500: is a SyntaxError, and Python will point right at it.
Notice what marks where the if block starts and ends: indentation. There are no braces or end keywords in Python — the 4 spaces are the syntax. Every line indented under the if belongs to it; the first line back at the left margin doesn't. Forget the indent and you'll meet the classic beginner error:
if total > 500:
print("big order")IndentationError: expected an indented block after 'if' statement on line 1
Use 4 spaces per level (VS Code inserts them when you press Tab), be consistent, and this error disappears from your life.
One more thing about conditions, in one paragraph: Python will accept any value where a condition goes, not just True/False. Empty things count as false — 0, "", an empty list [], and None — and everything else counts as true. So if order: reads as "if the order has anything in it", which is exactly how you'll see working Python written.
Lists and the for loop
An order isn't one value — it's a sequence of them. Python's sequence container is the list: square brackets, items in order, positions counted from 0:
order = ["steam momo", "chai"]
print(order[0]) # first item — indexes start at 0
print(len(order)) # how many items
order.append("jhol momo") # add to the end
print(order)
print(order[-1]) # -1 counts from the end
print(order[0:2]) # a slice: items 0 and 1 (2 is excluded)steam momo
2
['steam momo', 'chai', 'jhol momo']
jhol momo
['steam momo', 'chai']
Here's the surprise the name-tag model predicted. A list is one value, so two names can tag it:
mine = ["chai"]
yours = mine # second tag, SAME list
yours.append("steam momo")
print(mine)['chai', 'steam momo']
mine changed because there is only one list — .append changed the value both tags point at. Assignment never copies; it re-points tags. (When you truly want a copy: yours = list(mine).)
Now the move that makes lists powerful: the for loop. Read for price in prices: as "run the indented block once per item, with the name price tagging the current item each time". No counter, no off-by-one — the loop walks the list for you. The classic pattern is accumulation: start a total at 0, add each item to it:
prices = [150, 180, 200, 60]
total = 0
for price in prices:
total = total + price
print(total)590
Watch it run one pass at a time — the moving highlight is the loop, the table is the two names:
Note what the sim's last step tells you: price is not special loop machinery — it's an ordinary name, re-tagged to the next item on every pass, still tagging the last item (60) after the loop ends. Everything is names and values, all the way down.
Dicts: the lookup container
A list answers "what's at position 2?". But a menu doesn't work by position — it works by name: you ask "what does chai cost?" and get a price back. That lookup shape is Python's second container, the dict: curly braces, key: value pairs, and you look up by key:
menu = {"steam momo": 150, "fried momo": 180, "jhol momo": 200, "chai": 60}
print(menu["chai"])
print(menu["fried momo"])60
180
Ask for a key that isn't there and you get an error — unless you ask politely with .get(), which returns None (or a default you choose) instead of crashing:
print(menu["pizza"])KeyError: 'pizza'print(menu.get("pizza")) # None — no crash
print(menu.get("pizza", 0)) # 0 — your chosen fallbackNone
0
Assigning to a key adds it (or replaces it), and .items() lets a for loop walk the pairs — dicts remember insertion order, so you get them back in the order you wrote them:
menu["coke"] = 90 # new key → added
menu["chai"] = 70 # existing key → price updated
for name, price in menu.items():
print(f"{name}: Rs {price}")steam momo: Rs 150
fried momo: Rs 180
jhol momo: Rs 200
chai: Rs 70
coke: Rs 90
One forward-looking paragraph, because this shape follows you everywhere: when you later query a database, each row arrives shaped exactly like this dict — column name → value, row["price"], loop the pairs to display it. Learn dicts well here and PostgreSQL 101 will feel like familiar ground with SQL in front of it.
Functions
You've now written the same totaling loop twice. A function turns a block you'd otherwise repeat into a named recipe: def defines it (nothing runs yet), calling it by name runs it, and return hands the result back to whoever called:
def order_total(prices):
total = 0
for price in prices:
total = total + price
return total
bill = order_total([150, 180, 60])
print(bill)
print(order_total([200, 200]))390
400
Vocabulary: prices is a parameter — a name that gets tied to whatever value you pass in (the argument) each time the function is called.
Now the #1 beginner confusion, spelled out: return is not print. print shows a value on the screen for humans and hands back nothing; return hands the value back to the program, so the caller can keep computing with it. A function that prints instead of returning looks fine until you try to use its result:
def bad_total(prices):
total = 0
for price in prices:
total = total + price
print(total) # shows 390 on screen... and that's ALL it does
bill = bad_total([150, 180, 60])
print(bill)390
None
The 390 came from inside bad_total. But the function returned nothing, so Python quietly handed back None — and that's what landed in bill. Rule of thumb: functions return; only the outermost layer of your program prints.
One more property to see once: names created inside a function — the parameter and anything assigned in the body — live in a private frame that exists only during the call. At return, the frame is destroyed and those names are gone:
print(order_total([150, 180, 60]))
print(total) # 'total' only existed inside the call390
NameError: name 'total' is not defined
Watch one call happen, frame by frame:
Put it together
Everything above, in one ~25-line program. Type it into shop.py — a menu dict, an order list, a function that totals an order by looking each item up, a decision about delivery, and an f-string receipt:
menu = {
"steam momo": 150,
"fried momo": 180,
"jhol momo": 200,
"chai": 60,
}
def order_total(order):
total = 0
for item in order:
total = total + menu[item]
return total
def print_receipt(order):
print("=== momo shop ===")
for item in order:
print(f" {item} — Rs {menu[item]}")
total = order_total(order)
if total >= 500:
print(" delivery — free")
else:
total = total + 60
print(" delivery — Rs 60")
print(f"TOTAL: Rs {total}")
order = ["steam momo", "jhol momo", "chai"]
print_receipt(order)
Run python3 shop.py:
=== momo shop ===
steam momo — Rs 150
jhol momo — Rs 200
chai — Rs 60
delivery — Rs 60
TOTAL: Rs 470
Read the program top to bottom and name what you see: values with types, names tagging them, a dict looked up by key, a list walked by a for loop, an accumulator, an if choosing a path, functions returning values, f-strings printing the result. That's the whole guide — and it's most of everyday Python.
Before moving on, break it on purpose: add "coke": 90 to the menu and order one; order a "pizza" and read the KeyError; change >= 500 and watch delivery flip. When editing this program feels comfortable — not memorized, comfortable — you have exactly the prerequisite for the next stop: Object-oriented Python, where this same momo-shop data learns to carry its own behavior.
Takeaways
- Everything is a value with a type —
int,float,str,bool,Nonecover this whole guide;type(x)asks, and the type decides what operators mean. - A name is a tag, not a box — assignment re-points the tag and never copies the value; two names on one value don't watch each other, but they do share mutations to a list.
- Indentation is the syntax — 4 spaces marks what belongs to an
if,for, ordef; the first true branch of anif/elif/elsewins and the rest are skipped. - Lists are for order, dicts are for lookup — walk a list with
for item in items:, look up a dict by key withmenu["chai"]or the crash-proofmenu.get(key, default). - Functions
return, humans getprint—returnhands a value back to the caller to compute with;printonly shows pixels and hands backNone. Local names die when the call returns. - f-strings are your output tool for everything ahead —
f"{name} costs Rs {price}"— learn them on day one.
References
- Python Software Foundation. (n.d.). Built-in types (Python documentation). Retrieved August 22, 2026, from https://docs.python.org/3/library/stdtypes.html
- Python Software Foundation. (n.d.). Data structures (The Python tutorial, chapter 5). Retrieved August 22, 2026, from https://docs.python.org/3/tutorial/datastructures.html
- Python Software Foundation. (n.d.). Lexical analysis — keywords (The Python language reference). Retrieved August 22, 2026, from https://docs.python.org/3/reference/lexical_analysis.html
- Python Software Foundation. (n.d.). The Python tutorial. Retrieved August 22, 2026, from https://docs.python.org/3/tutorial/