Press m or Esc to close
Everything from Class XI that Class XII will stand on — types, operators, control flow, strings, lists, tuples, dictionaries. Taught in programs, not paragraphs.
Class XII does not teach you Python. It teaches you what to do with Python.
There is not one new idea in this chapter. But a student who is shaky here will be shaky in every remaining chapter — because file handling is loops over strings, and a database result is a list of tuples.
if, while, forBlue frame = what you wrote. Green frame = what the computer printed. Red frame = what went wrong.
Before the output appears — say it out loud.
“Predict the output” is not a classroom game. Open any CBSE paper: Find the output of the following code is worth 2–3 marks, every year, in every section. It is the cheapest mark in the paper, and the one most often lost.
Usually not the logic — the formatting. A missing space, a missing quote
inside a list, 3 written where Python prints 3.0.
Python's output is exact, and so is the marking.
Every green box in this deck is real output from a real run — spacing, quotes and all. Copy it exactly as you see it.
types.pymarks = 87 name = "Aarav" pct = 87.5 passed = True print(marks, type(marks)) print(name, type(name)) print(pct, type(pct)) print(passed, type(passed))
Data type — the classification of a value that decides what may be stored in it and which operations are allowed on it.
Python did not print int. It printed <class 'int'>.
In an exam, write it the way Python writes it.
| Type | Literal | Mutable? | Ordered? | Used for |
|---|---|---|---|---|
int | 87 | — | — | whole numbers, counters |
float | 87.5 | — | — | averages, percentages |
bool | True / False | — | — | conditions |
str | "Aarav" | No | Yes | text |
list | [1, 2, 3] | Yes | Yes | a changing collection |
tuple | (1, 2, 3) | No | Yes | a fixed record |
dict | {"a": 1} | Yes | Yes | lookup by key |
bool is a subtype of int. True behaves as
1 and False as 0 in arithmetic — which is
why True + True gives 2 instead of an error.
This one idea explains half the “surprising output” questions in the paper.
Variable — a name bound to an object in memory.
= performs binding, not copying: after y = x,
both names refer to the same object.
is and id()labels.pyx = 10 print(id(x) == id(10)) y = x print(x is y) x = x + 1 print(x, y) print(x is y)
x + 1 built a brand-new int object 11 and
re-pointed the name x at it. y never moved —
it is still on 10.
id(obj) returns the object's identity — its address in memory.
a is b asks “the same object?”;
a == b asks “the same value?”
is and == disagree?
Whenever two separate objects happen to hold equal values.
p = [1, 2] · q = [1, 2]p == q → True — the contents matchp is q → False — they are two different list objects
With small integers and short strings Python often reuses one cached object, which
is why id(x) == id(10) printed True above.
Do not rely on that — it is an internal optimisation, not a language rule,
and it stops at 256. In an answer, always compare values with
==; reserve is for x is None.
immutable — strs = "hello" t = s s = s.upper() print(s, t)
.upper() cannot change the string —
it returns a new one. t is untouched.
mutable — lista = [1, 2, 3] b = a a.append(4) print(a, b) print(a is b)
.append() changed the object itself.
Both names see it, because there is only one object.
Mutable — an object whose contents can be changed in place
after it is created: list, dict, set.
Immutable — an object that cannot be changed after creation;
any “change” produces a new object: int, float,
bool, str, tuple.
Predict the exact output.
x = 5 y = x x = x + 5 print(x, y) L1 = [1, 2] L2 = L1 L1.append(3) print(L1, L2)
Why the two halves differ: x + 5 rebinds x
to a new int, so y keeps the old one. L1.append(3)
mutates the one list that both L1 and L2 point at,
so both appear to change.
convert.pyprint(int("25") + 5) print(float("3.5") * 2) print(str(25) + "5") print(int(9.99)) print(int(True), float(7))
str(25) + "5" is concatenation, not addition →
"255".
int(9.99) is 9, not 10. int()
truncates towards zero; it does not round. Use
round(9.99) if you want 10.
input() always returns a stringThe most common runtime bug in a Class XII answer sheet.
WRONGn = input("Enter a number: ") print(n * 2)
Not 10. n is the string
"5", and "5" * 2 repeats it.
FIXEDage = input("Enter your age: ") print(age, type(age)) age = int(age) print("Next year:", age + 1)
Written as one line it is
age = int(input("Enter your age: ")) — that is the form the marking
scheme prints, and it is what you should write. But you must be able to explain
why the int() is there.
main.py1s = "abc" 2n = int(s) 3print(n)
This exact traceback is where Chapter 3 — Exception Handling begins.
int(input()) is the classic thing to wrap in
try / except ValueError.
The last line names the exception and explains it. The lines above show
which line of your file raised it. The paper asks you to name the
exception — ValueError — so learn to find it instantly.
arith.pya = 17 b = 5 print(a + b) print(a - b) print(a * b) print(a / b) print(a // b) print(a % b) print(a ** 2)
17 / 5 prints 3.4 — a float.
In Python 3, / always yields a float, even when the division
is exact: 10 / 5 is 2.0, not 2.
/ vs // vs %divide.pyprint(7 / 2, 7 // 2, 7 % 2) print(-7 / 2, -7 // 2, -7 % 2) print(7.0 // 2, 7 % 2.0) print(divmod(17, 5))
-7 // 2 is -4
// is floor division — it moves to the next lower
whole number, not towards zero. -3.5 floors to -4.
If either operand is a float, the result is a float —
7.0 // 2 gives 3.0, not 3.
And divmod(a, b) returns the pair
(a // b, a % b) as a tuple, which is why it printed with
brackets.
Because Python guarantees this identity for every pair of integers:
(a // b) * b + (a % b) == aCheck it with a = -7, b = 2: (-4) × 2 + 1 = -8 + 1 = -7 ✓.
If // truncated to -3 instead, then % would
have to give -1 to keep the identity, and the remainder would go
negative.
Keeping % non-negative for a positive divisor is what makes
n % 2 == 0, cyclic indexing and digit extraction behave for negative
numbers too. Rule to remember: the sign of % follows the
divisor — so -7 % 2 is 1, but
7 % -2 is -1.
precedence.pyprint(2 + 3 * 4) print((2 + 3) * 4) print(2 ** 3 ** 2) print(-3 ** 2) print(10 - 4 - 3) print(100 / 10 / 2)
2 ** 3 ** 2 is right-associative:
2 ** (3 ** 2) = 2 ** 9 = 512,
not 8 ** 2 = 64.
-3 ** 2 — ** binds tighter than the unary minus,
so it is -(3 ** 2) = -9, not 9.
10 - 4 - 3 is (10 - 4) - 3 = 3.
100 / 10 / 2 is (100 / 10) / 2 = 5.0 —
a float, because / was used.
Memorise the order top-to-bottom. Higher in the list binds tighter:
() → ** → unary -x →
* / // % → + - →
< <= > >= == != →
not → and → or
** sits above the unary minus, and that is the entire reason
-3 ** 2 is -9. But (-3) ** 2 is
9, and 2 ** -1 is 0.5 — there the minus is
on the right of **, where nothing competes with it.
In the exam: if you are unsure, insert the brackets yourself and write the bracketed form as your working. It costs nothing and it earns the mark.
logic.pyx = 12 y = 5 print(x > y, x == y, x != y) print(x > y and y > 10) print(x > y or y > 10) print(not (x > y)) print(5 < x < 20)
5 < x < 20 is legal Python and means
5 < x and x < 20. Most other languages do not allow it —
and it reads exactly like the mathematics.
= assigns; == compares.
Writing if x = 5: is a SyntaxError, not a logic bug —
Python refuses to run the program at all.
membership.pynums = [10, 20, 30] print(20 in nums) print(25 in nums) print("ell" in "hello") p = [1, 2] q = [1, 2] print(p == q) print(p is q)
Membership operators — in and
not in; test whether a value occurs inside a sequence.
Identity operators — is and
is not; test whether two names refer to the same object.
For strings, in looks for a substring, not just a single
character — which is why "ell" in "hello" is True.
augmented.pytotal = 100 total += 20 print(total) total -= 5 print(total) total *= 2 print(total) total //= 7 print(total) total **= 2 print(total)
| Statement | total |
|---|---|
| start | 100 |
| total += 20 | 120 |
| total -= 5 | 115 |
| total *= 2 | 230 |
| total //= 7 | 32 |
| total **= 2 | 1024 |
230 // 7 is 32 — remainder 6, discarded.
Had it been /=, total would have become
32.857142857142854 and everything after it would be a float.
Predict the exact output — three lines.
print(15 // 4, 15 % 4, 15 / 4) print(2 ** 3 ** 2) print(10 > 5 and 5 > 10 or 3 < 4)
Line 3, worked out: and binds tighter than or,
so it reads (True and False) or True → False or True →
True.
if / elif / elsegrade.pymarks = 72 if marks >= 90: grade = "A" elif marks >= 75: grade = "B" elif marks >= 60: grade = "C" else: grade = "D" print("Grade:", grade)
Python tests the branches top to bottom and stops at the first True.
72 fails >= 90, fails >= 75, passes
>= 60 — and the else is never reached.
Writing four separate if statements instead of
elif. Then every true test runs, and a student who scored
95 ends up with grade = "D".
Python has no braces. The indent is what puts a statement inside the
if. Four spaces, consistently. A stray indent is an
IndentationError, and the program does not run at all.
while — and how to dry-run onedigitsum.pyn = 4823 total = 0 while n > 0: d = n % 10 total = total + d n = n // 10 print("Digit sum =", total)
| Pass | n at test | d | total | n after |
|---|---|---|---|---|
| 1 | 4823 | 3 | 3 | 482 |
| 2 | 482 | 2 | 5 | 48 |
| 3 | 48 | 8 | 13 | 4 |
| 4 | 4 | 4 | 17 | 0 |
| — | 0 | 0 > 0 is False → the loop ends | ||
n % 10 peels off the last digit;
n // 10 removes it. Together they walk a number from right to
left. This appears every single year — digit sum, reverse a number, count digits,
palindrome number.
for and range()ranges.pyfor i in range(5): print(i, end=" ") print() for i in range(2, 9, 3): print(i, end=" ") print() for i in range(10, 0, -3): print(i, end=" ") print() print(list(range(5, 5)))
range(start, stop, step) — generates integers from start (default 0), advancing by step (default 1), and stopping before stop. stop is never included.
① range(2, 9, 3) stops at 8 — the next value, 11, is past 9.
② A negative step counts down and still excludes the stop, so
range(10, 0, -3) never reaches 0.
③ range(5, 5) is empty — the loop body never runs.
④ end=" " replaces print's automatic newline,
which is why each of the first three lines carries a trailing space.
break and continuejumps.pyfor n in range(1, 8): if n == 3: continue if n == 6: break print(n, end=" ") print() print("loop over")
| n | n == 3? | n == 6? | printed |
|---|---|---|---|
| 1 | No | No | 1 |
| 2 | No | No | 2 |
| 3 | Yes → continue | — | — |
| 4 | No | No | 4 |
| 5 | No | No | 5 |
| 6 | No | Yes → break | — |
| 7 | never reached | ||
continue — abandon the current iteration and go
straight to the next one.
break — abandon the whole loop immediately;
execution resumes at the first statement after the loop.
pattern.pyfor i in range(1, 5): for j in range(1, i + 1): print(j, end="") print()
| i | inner range | line printed |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1, 2 | 12 |
| 3 | 1, 2, 3 | 123 |
| 4 | 1, 2, 3, 4 | 1234 |
The bare print() on the amber line is indented under the
outer loop, so it runs once per row. Move it one level further in and you
get 1, 1, 2, 1, 2,
3… on separate lines instead.
Indentation is the answer to “why is the output shaped like that”.
How many times does this loop run, and what is the output?
i = 1 while i < 20: print(i, end=" ") i = i * 3 print()
Three iterations.
| Pass | i at test | i < 20? | printed | i after |
|---|---|---|---|---|
| 1 | 1 | True | 1 | 3 |
| 2 | 3 | True | 3 | 9 |
| 3 | 9 | True | 9 | 27 |
| 4 | 27 | False | loop ends | |
27 is never printed. The test happens before the body, so the value
that fails the test never reaches print.
index.pys = "COMPUTER" print(len(s)) print(s[0], s[3], s[7]) print(s[-1], s[-8]) print(s[0] + s[-1])
s[start : stop : step]slice.pys = "COMPUTER" print(s[0:4]) print(s[4:]) print(s[:3]) print(s[:]) print(s[1:7:2]) print(s[::-1]) print(s[-4:-1]) print(s[6:2])
s[::-1] — the standard reverse a string idiom. Learn it;
it is asked directly.
s[6:2] — start is after stop with a positive step, so the slice is
empty. print("") still emits its newline, which is the blank
final line in the output.
s[30] on an 8-character string crashes with
IndexError. s[0:30] does not — it simply gives you what
exists. This asymmetry is examined.
WRONG — main.py1s = "COMPUTER" 2s[0] = "X"
FIXEDs = "COMPUTER" s = "X" + s[1:] print(s)
You cannot edit a string in place. You build a new string —
usually by slicing around the part you want to change and joining with
+ — and rebind the name to it.
It is TypeError, not ValueError — the type
str is what refuses. You will meet the identical message shape for
tuples in §7.
traverse.pyword = "Python" # by character for ch in word: print(ch, end="-") print() # by index for i in range(len(word)): print(i, word[i])
Use for ch in word when you only need the character.
Use range(len(word)) when you also need the position —
for example, to compare a character with its neighbour.
end="-" puts a hyphen after every character, including the
last, so the line reads P-y-t-h-o-n-. Getting that final hyphen right
in your answer is worth the mark.
methods.pys = " Computer Science " print(s.strip()) print(s.upper()) print(s.strip().lower()) print(s.count("c")) print(s.find("Science")) print(s.replace("Science", "Sc")) print(s.strip().split()) print("-".join(["a", "b", "c"]))
① s.count("c") is 2, not 3 — it is
case-sensitive, and the capital C of “Computer” does not
count.
② The leading and trailing spaces are still there on lines 2 and 6.
No string method changes s itself — each returns a new string.
③ split() returns a list, printed with quotes:
['Computer', 'Science'].
tests.pys = "Comp123" print(s.isalpha(), s.isdigit(), s.isalnum()) print("comp".isalpha(), "123".isdigit()) print("A".isupper(), "a".islower()) print("hello world".title())
These methods test every character. "Comp123" contains
digits, so it is not all-alphabetic; it contains letters, so it is not
all-digits; but it is entirely letters-or-digits, so isalnum()
is True.
Counting vowels, consonants, digits and spaces in a sentence is a standard 3-mark
program. It is isalpha() + isdigit() + a membership test,
and nothing more. You will write it at the end of this chapter.
vowels.pytxt = "SUCCESS" vowels = 0 for ch in txt: if ch in "AEIOU": vowels = vowels + 1 print("Vowels =", vowels) print("Others =", len(txt) - vowels)
| ch | in "AEIOU"? | vowels |
|---|---|---|
| S | No | 0 |
| U | Yes | 1 |
| C | No | 1 |
| C | No | 1 |
| E | Yes | 2 |
| S | No | 2 |
| S | No | 2 |
ch in "AEIOU" and not five ors
if ch=="A" or ch=="E" or … is accepted, but the membership form is
shorter, harder to get wrong, and reads as English. If the text may be lowercase,
test ch.upper() in "AEIOU".
Predict all four lines.
s = "PYTHON2024" print(s[2:6]) print(s[::3]) print(s[-4:]) print(s[::-2])
| Slice | How to read it |
|---|---|
s[2:6] | indices 2, 3, 4, 5 → T H O N |
s[::3] | whole string, every 3rd → indices 0, 3, 6, 9 → P H 2 4 |
s[-4:] | last four characters → 2 0 2 4 |
s[::-2] | from the end, every 2nd → indices 9, 7, 5, 3, 1 → 4 0 N H Y |
Without running it — what does s[::-1][::-1] give, for
any string s? Say why in one sentence.
s itself. Reversing a reversal restores the original order — and note
that s was never modified, because each slice returns a new
string.
list1.pymarks = [45, 78, 92, 61, 88] print(marks) print(len(marks)) print(marks[0], marks[-1]) print(marks[1:4]) print(marks[::-1]) print(marks + [100]) print([0] * 3)
List — an ordered, mutable collection of values, written in square brackets, indexed from 0, and allowed to hold items of different types.
Indexing, negative indexing, slicing, +, *,
in, len() — identical on lists. They are both
sequences. Only one thing differs: a list can be changed.
mutate.pymarks = [45, 78, 92, 61, 88] marks[0] = 50 print(marks) marks[1:3] = [80, 95] print(marks) del marks[4] print(marks)
s[0] = "X" on a string raised
TypeError. marks[0] = 50 on a list simply works.
That single difference is what “mutable” means.
L[1:4] = [9] replaces three items with one, and the
list gets shorter.
It removes the slice and splices in whatever is on the right — so the list's length changes by (items in, items out).
L = [1, 2, 3, 4, 5]L[1:4] = [9] → [1, 9, 5] — 3 out, 1 in, length 5 → 3L[1:2] = [7, 8] → [1, 7, 8, 5] — 1 out, 2 in, length 3 → 4
The right-hand side must be iterable. L[1:4] = 9 is a
TypeError — but L[1:4] = "ab" quietly splices in
'a' and 'b' as two separate items, because a string is
iterable. That is the trap.
methods.pyL = [10, 20, 30] L.append(40) print(L) L.insert(1, 15) print(L) L.extend([50, 60]) print(L) print(L.pop()) print(L.pop(0)) print(L) L.remove(30) print(L)
pop returns; remove does not
L.pop() removes and returns the last item — which is why
60 and 10 got printed.
L.remove(30) removes by value and returns
None.
L.remove(30) takes a value; L.pop(0) takes an
index. Confusing them is a guaranteed lost mark.
Writing L = L.append(40) sets L to None —
append changes the list in place and returns nothing.
append vs extend — the classicappext.pyA = [1, 2] B = [1, 2] A.append([3, 4]) B.extend([3, 4]) print(A) print(B) print(len(A), len(B))
append(x) — adds x itself as one new
item, whatever x is.
extend(seq) — adds each item of seq
separately.
len(A) is 3, not 4 — its third item is a whole list.
A[2] is [3, 4] and A[2][0] is
3. That nesting is exactly how you will hold a table of records
later in the year.
sorting.pyL = [5, 2, 9, 1] L.sort() print(L) L.sort(reverse=True) print(L) L.reverse() print(L) print(sorted([3, 1, 2])) print(L.index(9), L.count(5))
L.sort() vs sorted(L)
L.sort() changes L and returns None.
sorted(L) leaves L alone and returns a new sorted list.
So print(L.sort()) prints None — a favourite
one-mark trap.
After the reverse, L is [1, 2, 5, 9], so
L.index(9) is 3 — the position of 9 — and
L.count(5) is 1 — how many times 5 occurs. Two very
different questions.
b = a makes an alias. c = a[:] makes a copy.alias.pya = [1, 2, 3] b = a c = a[:] a.append(4) print("a =", a) print("b =", b) print("c =", c) print(a is b, a is c)
= never copies a list. To get an independent list write
a[:], list(a) or a.copy().
stats.pymarks = [45, 78, 92, 61, 88] total = 0 for m in marks: total = total + m print("Total =", total) print("Average =", total / len(marks)) print("Highest =", max(marks)) print("Lowest =", min(marks)) print("Sum =", sum(marks))
The loop and sum(marks) both give 364. Write the loop
when the question says “without using built-in functions” — read the question
carefully, because it often does.
build.pynums = [12, 7, 25, 8, 31, 4] evens = [] for n in nums: if n % 2 == 0: evens.append(n) print(evens) squares = [n * n for n in nums if n % 2 != 0] print(squares)
① start with an empty list ② loop over the source
③ append the ones that qualify.
You will use this shape in every remaining chapter — filtering records read
from a file, from a CSV, from a database.
The list comprehension on the last line is correct and elegant, and 7, 25 and 31 are the odd ones — 49, 625, 961. But unless the question asks for it, write the explicit loop: it is what the marking scheme shows, and it earns method marks even if the final answer slips.
Predict the exact output — three lines.
L = [1, 2, 3, 4, 5] print(L[1:4]) L[1:4] = [9] print(L) print(L * 2)
Line 2 removed the three items 2, 3, 4 and spliced 9 in
their place, so the list shrank from 5 items to 3. L * 2 then
repeats the whole list — it does not double the numbers.
tuple1.pyt = (10, 20, 30, 40) print(t, type(t)) print(t[0], t[-1]) print(t[1:3]) print(len(t), max(t), sum(t)) single = (5) real = (5,) print(type(single), type(real))
(5) is just 5 in brackets — an int.
What makes a tuple is the comma, not the brackets. So a single-element
tuple must be written (5,).
t = 10, 20, 30 is a perfectly good tuple. This is why a function can
appear to “return several values” — it is really returning one tuple.
WRONG — main.py1t = (10, 20, 30) 2t[0] = 99
Word for word the string error from §5.3, with
tuple in place of str.
but look at thist = (10, [20, 30], 40) t[1].append(35) print(t)
The tuple still holds exactly the same three objects — nothing in the tuple was reassigned. What changed is the list inside it, and that list is mutable.
A tuple's bindings are frozen, not its contents. t[1] = [...]
is forbidden; t[1].append(...) is not.
unpack.pypoint = (3, 7) x, y = point print(x, y) a, b = 1, 2 a, b = b, a print(a, b) first, *rest = (10, 20, 30, 40) print(first, rest)
Packing — several values collected into one tuple
(t = 1, 2).
Unpacking — one tuple spread across several names
(x, y = t). The counts must match, or you get a
ValueError.
a, b = b, a works
The right-hand side is evaluated first, into the tuple (2, 1).
Only then is it unpacked into a and b. No temporary
variable is needed — this is the swap the paper expects.
*rest collects the remainder as a list, not a tuple —
which is why it printed [20, 30, 40].
tconv.pyt = (5, 3, 5, 8, 5) print(t.count(5)) print(t.index(8)) print(sorted(t)) print(tuple([1, 2, 3])) print(list(t))
sorted(t) returns a list — square brackets — even though
it was given a tuple. A tuple has no .sort() at all; there is
nothing to sort in place.
| list | tuple | |
|---|---|---|
| Written with | [ ] | ( ) |
| Mutable | Yes | No |
| append / insert / remove / sort | Yes | No |
| count, index, len, max, min, sum | Yes | Yes |
| Indexing and slicing | Yes | Yes |
| Usable as a dictionary key | No | Yes |
| Use it for | data that grows or changes | a fixed record — a row, a point |
Predict the exact output — four lines.
T = (1, 2, 3, 4, 5) print(T[1:4]) print(T + (6,)) print(T * 2) print(3 in T)
Note that + and * both worked on an “immutable” tuple.
They did not change T — each built a new tuple.
T is still (1, 2, 3, 4, 5).
Why would T + 6 fail, when T + (6,) works?
+ on a tuple means concatenate two tuples, and 6
is not a tuple. The error is
TypeError: can only concatenate tuple (not "int") to tuple. The
trailing comma in (6,) is what makes it a one-element tuple — the
same point as §7.1.
A list finds things by position. A dictionary finds them by name.
dict1.pystudent = {"name": "Riya", "cls": 12, "marks": 88} print(student) print(student["name"]) print(len(student)) student["marks"] = 91 student["stream"] = "Science" print(student)
Dictionary — a mutable collection of key : value pairs, written in curly braces, in which a value is looked up by its key rather than by a position.
d[k] = v updates if the key exists and inserts if it
does not. There is no separate “add” method — which is why
"marks" was overwritten while "stream" was appended.
WRONG — main.py1student = {"name": "Riya", "cls": 12} 2print(student["marks"])
SAFEstudent = {"name": "Riya", "cls": 12} print(student.get("marks")) print(student.get("marks", 0)) print("cls" in student) print("marks" in student)
get never raises
d.get(k) returns None for a missing key;
d.get(k, default) returns your default instead.
in tests keys, not values
"cls" in student is True, but 12 in student
is False — 12 is a value. To search values, write
12 in student.values().
dmethods.pyd = {"a": 1, "b": 2, "c": 3} print(d.keys()) print(d.values()) print(d.items()) print(d.pop("b")) print(d) d.update({"c": 30, "z": 26}) print(d)
These are not lists. Python prints
dict_keys([...]), and the marking scheme expects that wrapper.
If you want a real list, write list(d.keys()).
d.items() gives a sequence of (key, value) tuples — tuples
again, exactly as in §7.
d.update() overwrote the existing "c" and
added the new "z" — the same rule as d[k] = v.
walk.pyd = {"Maths": 95, "Physics": 78, "CS": 99} for k in d: print(k, "->", d[k]) for k, v in d.items(): print(k, v) print(sum(d.values()) / len(d))
for k in d loops over the KEYS
Not the values, and not the pairs. If you want both, use
d.items() and unpack into two names — the tuple unpacking from
§7.3, used for real.
272 / 3 prints 90.66666666666667 — not
90.67. Python shows full float precision unless you ask it not to
with round() or a format specifier.
freq.pytext = "banana" freq = {} for ch in text: if ch in freq: freq[ch] = freq[ch] + 1 else: freq[ch] = 1 print(freq)
| ch | already a key? | freq after |
|---|---|---|
| b | No | {'b': 1} |
| a | No | {'b': 1, 'a': 1} |
| n | No | {'b': 1, 'a': 1, 'n': 1} |
| a | Yes | {'b': 1, 'a': 2, 'n': 1} |
| n | Yes | {'b': 1, 'a': 2, 'n': 2} |
| a | Yes | {'b': 1, 'a': 3, 'n': 2} |
A dictionary keeps keys in insertion order — the order in which each key
was first added. 'a' was seen three times but inserted once,
so it stays in second place.
Both answers appear in circulation, so be precise about which one you mean.
d[0]
to get "the first pair". There is no positional index.Older NCERT material and many question banks were written for Python 3.5 and call dictionaries "unordered", because back then the print order really was unpredictable. Every current Python prints insertion order, and that is what you should write when asked to predict output.
If a question explicitly asks "are dictionaries ordered?", the safe full-mark answer is: a dictionary is an unindexed collection — items are accessed by key, not position — although since Python 3.7 it preserves insertion order.
WRONG — main.py1d = {} 2d[[1, 2]] = "list key"
A key must be immutable — int, float,
str or tuple. A value may be anything at all,
including a list or another dictionary.
Python locates a value from a key's hash. If the key could change after insertion, its hash would change and the value would become unreachable — so mutable objects are refused outright.
seats = {(1, "A"): "Riya",
(1, "B"): "Aarav"}
A tuple key — immutable, so allowed. This is the standard way to key a grid or a seat map.
Predict the exact output — three lines.
d = {1: "one", 2: "two"}
d[3] = "three"
d[1] = "ONE"
print(d)
print(list(d.keys()))
print(len(d))
d[3] added a pair; d[1] replaced a value.
So the length is 3, not 4. And key 1 keeps its original
position — updating a value does not re-insert the key.
| You need to… | Use | Because |
|---|---|---|
| hold text | str | it is characters, in order, and never edited in place |
| collect marks and keep adding to them | list | ordered and mutable |
| hold one fixed record — (name, class, marks) | tuple | the shape must not change |
| look something up by a name or an ID | dict | access by key, not by position |
| count how often each item occurs | dict | item → count is exactly key → value |
| use a pair as a lookup key | tuple | a list is unhashable; a tuple is not |
“Does this thing need to change after I build it?” Yes → list or dictionary. No → string or tuple.
| Exception | Raised by | Message you must be able to quote |
|---|---|---|
ValueError | int("abc") | invalid literal for int() with base 10: 'abc' |
TypeError | s[0] = "X" | 'str' object does not support item assignment |
TypeError | t[0] = 99 | 'tuple' object does not support item assignment |
KeyError | d["missing"] | 'missing' |
TypeError | d[[1, 2]] = x | unhashable type: 'list' |
In Chapter 3 you will stop these from crashing the program:
try / except ValueError,
except KeyError, and the rest. You cannot handle an exception you
cannot name — which is why they were shown here first.
Write the output of the following code.
S = "Board2026" D = {} for ch in S: if ch.isdigit(): D["digits"] = D.get("digits", 0) + 1 elif ch.isupper(): D["upper"] = D.get("upper", 0) + 1 else: D["lower"] = D.get("lower", 0) + 1 print(D) print(S[::-1][:4]) print(len(S), S.count("o"))
Order of the keys: 'B' is met first, so 'upper'
is inserted first, then 'lower' for o a r d, then
'digits'. Insertion order, not alphabetical.
Second line: S[::-1] is "6202draoB", and
[:4] takes its first four characters.
Third line: count("o") is 1 — case-sensitive, and
"2026" contains no letter o.
Rewrite the following code after removing the errors, underlining each correction.
1L = [10, 25, 30, 41] 2for i in Range(len(L)): 3 if L[i] % 2 = 0: 4 print(L[i], "even") 5 else 6 print(L[i], "odd")
| Line | Error | Correction |
|---|---|---|
| 2 | Range — Python is case-sensitive | range |
| 3 | = assigns; a condition needs a comparison | == |
| 5 | missing colon after else | else: |
CorrectedL = [10, 25, 30, 41] for i in range(len(L)): if L[i] % 2 == 0: print(L[i], "even") else: print(L[i], "odd")
Write a program that counts the vowels, consonants, digits and other characters
in the string "Computer Science 2024".
count.pys = "Computer Science 2024" v = c = d = o = 0 for ch in s: if ch.isdigit(): d = d + 1 elif ch.isalpha(): if ch.lower() in "aeiou": v = v + 1 else: c = c + 1 else: o = o + 1 print("Vowels:", v, "Consonants:", c) print("Digits:", d, "Others:", o)
① testing isdigit() before isalpha()
so digits are never counted as consonants.
② ch.lower(), so a capital C and
S are handled.
③ the final else catching the two spaces — most answers
forget them.
Given L = [12, 45, 7, 45, 30, 3], print the largest and the
second largest value. Note that 45 occurs twice.
second.pyL = [12, 45, 7, 45, 30, 3] big = max(L) second = -1 for n in L: if n != big and n > second: second = n print("Largest =", big) print("Second largest =", second)
L.sort() then L[-2] gives 45, because the
sorted list is [3, 7, 12, 30, 45, 45]. It answers “second last
item”, not “second largest value”. The n != big test on the
amber line is what fixes it.
From words = ["sun", "planet", "moon", "galaxy"], build a dictionary
mapping each word to its length, then print the longest word.
wordlen.pywords = ["sun", "planet", "moon", "galaxy"] sizes = {} for w in words: sizes[w] = len(w) print(sizes) longest = "" for w in sizes: if sizes[w] > len(longest): longest = w print("Longest:", longest)
planet and not galaxy
Both have 6 letters. The test is strictly greater, so the first 6-letter word met keeps the title. Ties go to whoever arrived first — say so in your answer and you have shown you understand the loop.
Predict the exact output. Every line uses something from this chapter.
T = ("Riya", 12, 88) L = list(T) L[2] = L[2] + 5 T2 = tuple(L) print(T) print(T2) print(T is T2, T == T2)
Line 1 — T is unchanged. You never edited the tuple; you
converted it to a list, edited that, and built a second tuple. This
list ⇄ tuple round trip is the standard way to "modify" a tuple, and it is a
favourite exam question.
Line 3 — is is False because they are two separate
objects, and == is False because 88 ≠ 93. Had you written
T2 = tuple(list(T)) with no edit, is would still be
False but == would be True.
You have been writing straight-line programs. Chapter 2 lets you name a block of code, hand it arguments and get a value back — and the very first thing it will test is whether you really understood mutable arguments.
① A name is a label on an object — so passing a list into a function passes
the same list.
② None is what a function returns when you do not
return anything — you have already seen it from
list.sort().
③ Predict the output before you run it. Every single time.