CH 1 · PYTHON REVISION
Unit 1 · Computational Thinking and Programming

Chapter 1
Python, Revised

Everything from Class XI that Class XII will stand on — types, operators, control flow, strings, lists, tuples, dictionaries. Taught in programs, not paragraphs.

1.1 Why we start here

Class XII does not teach you Python. It teaches you what to do with Python.

What is coming this year

  • Functions and scope
  • Exception handling
  • File handling — text, binary, CSV
  • Stacks
  • Python with MySQL

What every one of them assumes

  • You can read a loop and say what it prints
  • You know a list changes and a string does not
  • You can slice
  • You can build and walk a dictionary
The point

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.

1.2 How this chapter runs

Seven stops

  1. Names, objects and types — what a variable really is
  2. Operators — and the ones that trip people up
  3. Control flowif, while, for
  4. Strings — indexing, slicing, immutability
  5. Lists — the workhorse
  6. Tuples — the frozen list
  7. Dictionaries — lookup by name

The rhythm of every slide

  1. A short, complete, runnable program — blue frame
  2. You predict what it prints
  3. The real output — green frame
Colour code — learn it now

Blue frame = what you wrote. Green frame = what the computer printed. Red frame = what went wrong.

1.3 The single most valuable habit

Before the output appears — say it out loud.

Why it matters

“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.

What loses the mark

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.

So

Every green box in this deck is real output from a real run — spacing, quotes and all. Copy it exactly as you see it.

2.1 Start with a program

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))
Four lines. What exactly? 🤔
Output87 <class 'int'> Aarav <class 'str'> 87.5 <class 'float'> True <class 'bool'>
Definition

Data type — the classification of a value that decides what may be stored in it and which operations are allowed on it.

Read the output carefully

Python did not print int. It printed <class 'int'>. In an exam, write it the way Python writes it.

2.2 The core types, on one screen

TypeLiteralMutable?Ordered?Used for
int87whole numbers, counters
float87.5averages, percentages
boolTrue / Falseconditions
str"Aarav"NoYestext
list[1, 2, 3]YesYesa changing collection
tuple(1, 2, 3)NoYesa fixed record
dict{"a": 1}YesYeslookup by key
The Mutable? column is the whole chapter. Almost every surprising output follows from it.
Careful

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.

2.3 A variable is a label, not a box

This one idea explains half the “surprising output” questions in the paper.

NAMES OBJECTS IN MEMORY x y 10 one int object x = 10 then y = x
Assignment binds a name to an object. It does not copy the object.
Definition

Variable — a name bound to an object in memory. = performs binding, not copying: after y = x, both names refer to the same object.

2.4 Proving it — 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)
What do the last two lines print? 🤔
OutputTrue True 11 10 False
What happened on the amber line

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.

Vocabulary

id(obj) returns the object's identity — its address in memory. a is b asks “the same object?”; a == b asks “the same value?”

When do is and == disagree?

Whenever two separate objects happen to hold equal values.

p = [1, 2] · q = [1, 2]
p == qTrue — the contents match
p is qFalse — 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.

2.5 Immutable vs mutable — same shape, two outcomes

immutable — strs = "hello"
t = s
s = s.upper()
print(s, t)
OutputHELLO hello

.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)
Output[1, 2, 3, 4] [1, 2, 3, 4] True

.append() changed the object itself. Both names see it, because there is only one object.

Definition

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.

2.6 Check yourself

Question 1

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)
Are you ready for the answer? 🤔
Answer
Output10 5 [1, 2, 3] [1, 2, 3]

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.

2.7 Type conversion

convert.pyprint(int("25") + 5)
print(float("3.5") * 2)
print(str(25) + "5")
print(int(9.99))
print(int(True), float(7))
The two amber lines — careful. 🤔
Output30 7.0 255 9 1 7.0
The two traps

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.

2.8 input() always returns a string

The most common runtime bug in a Class XII answer sheet.

WRONGn = input("Enter a number: ")
print(n * 2)
Output — user types 5Enter a number: 5 55

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)
Output — user types 17Enter your age: 17 17 <class 'str'> Next year: 18
Board style

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.

2.9 …and what happens when it cannot convert

main.py1s = "abc"
2n = int(s)
3print(n)
Which exception? Name it exactly. 🤔
TracebackTraceback (most recent call last): File "main.py", line 2, in <module> n = int(s) ^^^^^^ ValueError: invalid literal for int() with base 10: 'abc'
Forward link

This exact traceback is where Chapter 3 — Exception Handling begins. int(input()) is the classic thing to wrap in try / except ValueError.

Read a traceback bottom-up

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.

3.1 The seven arithmetic operators

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)
All seven. Write them down first. 🤔
Output22 12 85 3.4 3 2 289
The line everyone gets wrong

17 / 5 prints 3.4a float. In Python 3, / always yields a float, even when the division is exact: 10 / 5 is 2.0, not 2.

3.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))
The amber line is the hard one. 🤔
Output3.5 3 1 -3.5 -4 1 3.0 1.0 (3, 2)
Why -7 // 2 is -4

// is floor division — it moves to the next lower whole number, not towards zero. -3.5 floors to -4.

Also notice

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.

Why floor and not truncate?

Because Python guarantees this identity for every pair of integers:

(a // b) * b + (a % b) == a

Check 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.

3.3 Precedence — and two famous traps

precedence.pyprint(2 + 3 * 4)
print((2 + 3) * 4)
print(2 ** 3 ** 2)
print(-3 ** 2)
print(10 - 4 - 3)
print(100 / 10 / 2)
The two amber lines — think before you speak. 🤔
Output14 20 512 -9 3 5.0
Both traps explained

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.

Everything else runs left to right

10 - 4 - 3 is (10 - 4) - 3 = 3. 100 / 10 / 2 is (100 / 10) / 2 = 5.0 — a float, because / was used.

How do I never get this wrong?

Memorise the order top-to-bottom. Higher in the list binds tighter:

()** → unary -x* / // %+ -< <= > >= == !=notandor

** 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.

3.4 Relational and logical operators

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)
Five lines of True/False. 🤔
OutputTrue False True False True False True
Chained comparison

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.

The classic slip

= assigns; == compares. Writing if x = 5: is a SyntaxError, not a logic bug — Python refuses to run the program at all.

3.5 Membership and identity

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)
The last two lines. Same, or different? 🤔
OutputTrue False True True False
Definition

Membership operatorsin and not in; test whether a value occurs inside a sequence.
Identity operatorsis and is not; test whether two names refer to the same object.

Note

For strings, in looks for a substring, not just a single character — which is why "ell" in "hello" is True.

3.6 Augmented assignment

augmented.pytotal = 100
total += 20
print(total)
total -= 5
print(total)
total *= 2
print(total)
total //= 7
print(total)
total **= 2
print(total)
Track it in your head. 🤔
Output120 115 230 32 1024
Statementtotal
start100
total += 20120
total -= 5115
total *= 2230
total //= 732
total **= 21024
Watch the amber line

230 // 7 is 32 — remainder 6, discarded. Had it been /=, total would have become 32.857142857142854 and everything after it would be a float.

3.7 Check yourself

Question 2

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)
Are you ready for the answer? 🤔
Answer
Output3 3 3.75 512 True

Line 3, worked out: and binds tighter than or, so it reads (True and False) or TrueFalse or TrueTrue.

4.1 if / elif / else

grade.pymarks = 72

if marks >= 90:
    grade = "A"
elif marks >= 75:
    grade = "B"
elif marks >= 60:
    grade = "C"
else:
    grade = "D"

print("Grade:", grade)
OutputGrade: C
Why the order matters

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.

Common mistake

Writing four separate if statements instead of elif. Then every true test runs, and a student who scored 95 ends up with grade = "D".

Indentation is syntax

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.

4.2 while — and how to dry-run one

digitsum.pyn = 4823
total = 0

while n > 0:
    d = n % 10
    total = total + d
    n = n // 10

print("Digit sum =", total)
Trace it before you look. 🤔
Passn at testdtotaln after
1482333482
24822548
3488134
444170
00 > 0 is False → the loop ends
OutputDigit sum = 17
The pattern to memorise

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.

4.3 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)))
Four lines of output. 🤔
Output0 1 2 3 4 2 5 8 10 7 4 1 []
Definition

range(start, stop, step) — generates integers from start (default 0), advancing by step (default 1), and stopping before stop. stop is never included.

Four things to notice

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.

4.4 break and continue

jumps.pyfor n in range(1, 8):
    if n == 3:
        continue
    if n == 6:
        break
    print(n, end=" ")
print()
print("loop over")
Which numbers get printed? 🤔
nn == 3?n == 6?printed
1NoNo1
2NoNo2
3Yes → continue
4NoNo4
5NoNo5
6NoYes → break
7never reached
Output1 2 4 5 loop over
Definition

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.

4.5 Nested loops

pattern.pyfor i in range(1, 5):
    for j in range(1, i + 1):
        print(j, end="")
    print()
Four lines. What are they? 🤔
iinner rangeline printed
111
21, 212
31, 2, 3123
41, 2, 3, 41234
Output1 12 123 1234
Where the newline comes from

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”.

4.6 Check yourself

Question 3

How many times does this loop run, and what is the output?

i = 1
while i < 20:
    print(i, end=" ")
    i = i * 3
print()
Are you ready for the answer? 🤔
Answer

Three iterations.

Passi at testi < 20?printedi after
11True13
23True39
39True927
427Falseloop ends
Output1 3 9
The mark you lose

27 is never printed. The test happens before the body, so the value that fails the test never reaches print.

5.1 A string is an ordered sequence of characters

s = "COMPUTER" C O M P U T E R index 0 1 2 3 4 5 6 7 from the end -8 -7 -6 -5 -4 -3 -2 -1 len(s) = 8, so the last valid index is 7 — never 8
Every character has two addresses. Both are legal, and the paper uses both.
index.pys = "COMPUTER"
print(len(s))
print(s[0], s[3], s[7])
print(s[-1], s[-8])
print(s[0] + s[-1])
Output8 C P R R C CR

5.2 Slicing — 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])
Eight lines — the last one is a trick. 🤔
OutputCOMP UTER COM COMPUTER OPT RETUPMOC UTE
The two amber lines

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.

Slicing never raises IndexError

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.

5.3 Strings are immutable

WRONG — main.py1s = "COMPUTER"
2s[0] = "X"
Name the exception exactly. 🤔
TracebackTraceback (most recent call last): File "main.py", line 2, in <module> s[0] = "X" ~^^^ TypeError: 'str' object does not support item assignment
FIXEDs = "COMPUTER"
s = "X" + s[1:]
print(s)
OutputXOMPUTER
The rule

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.

Note the exception name

It is TypeError, not ValueError — the type str is what refuses. You will meet the identical message shape for tuples in §7.

5.4 Two ways to traverse a string

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])
OutputP-y-t-h-o-n- 0 P 1 y 2 t 3 h 4 o 5 n
Which one to use

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.

Notice the trailing hyphen

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.

5.5 String methods you must know

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"]))
Watch the spaces and the case. 🤔
OutputComputer Science COMPUTER SCIENCE computer science 2 11 Computer Sc ['Computer', 'Science'] a-b-c
Three places marks are lost

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'].

5.6 Testing what a string contains

tests.pys = "Comp123"
print(s.isalpha(), s.isdigit(), s.isalnum())
print("comp".isalpha(), "123".isdigit())
print("A".isupper(), "a".islower())
print("hello world".title())
The amber line surprises people. 🤔
OutputFalse False True True True True True Hello World
Why the first line is False False True

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.

Where you will use this

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.

5.7 A complete string program

vowels.pytxt = "SUCCESS"
vowels = 0

for ch in txt:
    if ch in "AEIOU":
        vowels = vowels + 1

print("Vowels =", vowels)
print("Others =", len(txt) - vowels)
SUCCESS — how many vowels? 🤔
chin "AEIOU"?vowels
SNo0
UYes1
CNo1
CNo1
EYes2
SNo2
SNo2
OutputVowels = 2 Others = 5
Why 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".

5.8 Check yourself

Question 4

Predict all four lines.

s = "PYTHON2024"
print(s[2:6])
print(s[::3])
print(s[-4:])
print(s[::-2])
Are you ready for the answer? 🤔
Answer
OutputTHON PH24 2024 40NHY
SliceHow 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
Challenge

Without running it — what does s[::-1][::-1] give, for any string s? Say why in one sentence.

Answer

s itself. Reversing a reversal restores the original order — and note that s was never modified, because each slice returns a new string.

6.1 The list — Python's workhorse

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)
Seven lines. Mind the brackets. 🤔
Output[45, 78, 92, 61, 88] 5 45 88 [78, 92, 61] [88, 61, 92, 78, 45] [45, 78, 92, 61, 88, 100] [0, 0, 0]
Definition

List — an ordered, mutable collection of values, written in square brackets, indexed from 0, and allowed to hold items of different types.

Everything you learnt about strings still applies

Indexing, negative indexing, slicing, +, *, in, len()identical on lists. They are both sequences. Only one thing differs: a list can be changed.

6.2 …and unlike a string, it 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)
Three lists. 🤔
Output[50, 78, 92, 61, 88] [50, 80, 95, 61, 88] [50, 80, 95, 61]
Compare with §5.3

s[0] = "X" on a string raised TypeError. marks[0] = 50 on a list simply works. That single difference is what “mutable” means.

Slice assignment does not have to match in length

L[1:4] = [9] replaces three items with one, and the list gets shorter.

What exactly does slice assignment do?

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 → 3
L[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.

6.3 The list methods that are examined

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)
Seven lines. Track the list. 🤔
Output[10, 20, 30, 40] [10, 15, 20, 30, 40] [10, 15, 20, 30, 40, 50, 60] 60 10 [15, 20, 30, 40, 50] [15, 20, 40, 50]
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.

Two traps

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 Noneappend changes the list in place and returns nothing.

6.4 append vs extend — the classic

appext.pyA = [1, 2]
B = [1, 2]

A.append([3, 4])
B.extend([3, 4])

print(A)
print(B)
print(len(A), len(B))
Same argument, two methods. 🤔
Output[1, 2, [3, 4]] [1, 2, 3, 4] 3 4
The distinction, in one line each

append(x) — adds x itself as one new item, whatever x is.
extend(seq) — adds each item of seq separately.

Read the lengths

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.

6.5 Sorting and searching

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))
Five lines. 🤔
Output[1, 2, 5, 9] [9, 5, 2, 1] [1, 2, 5, 9] [1, 2, 3] 3 1
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.

The last line

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.

6.6 Aliasing vs copying — draw it

a b c [1, 2, 3, 4] one list object — b = a [1, 2, 3] a second object — c = a[:] a.append(4) changes the object at the top — so b changes too
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)
Outputa = [1, 2, 3, 4] b = [1, 2, 3, 4] c = [1, 2, 3] True False
The rule

= never copies a list. To get an independent list write a[:], list(a) or a.copy().

6.7 Reading a whole list

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))
Five lines — one of them is a float. 🤔
OutputTotal = 364 Average = 72.8 Highest = 92 Lowest = 45 Sum = 364
Two ways, same answer

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.

6.8 Building a new list from an old one

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)
Two lists. 🤔
Output[12, 8, 4] [49, 625, 961]
The three-step pattern

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.

Good Python, but write the loop in the exam

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.

6.9 Check yourself

Question 5

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)
Are you ready for the answer? 🤔
Answer
Output[2, 3, 4] [1, 9, 5] [1, 9, 5, 1, 9, 5]

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.

7.1 A tuple is a list that cannot change

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))
The last line is the whole point. 🤔
Output(10, 20, 30, 40) <class 'tuple'> 10 40 (20, 30) 4 40 100 <class 'int'> <class 'tuple'>
The one-element tuple

(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,).

The brackets are optional

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.

7.2 Immutable — and one subtlety

WRONG — main.py1t = (10, 20, 30)
2t[0] = 99
TracebackTraceback (most recent call last): File "main.py", line 2, in <module> t[0] = 99 ~^^^ TypeError: 'tuple' object does not support item assignment

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)
Error, or does it work? 🤔
Output(10, [20, 30, 35], 40)
Why this is allowed

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.

Say it precisely

A tuple's bindings are frozen, not its contents. t[1] = [...] is forbidden; t[1].append(...) is not.

7.3 Packing, unpacking and the one-line swap

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)
Three lines. 🤔
Output3 7 2 1 10 [20, 30, 40]
Definition

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.

Why 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.

Note the last line

*rest collects the remainder as a list, not a tuple — which is why it printed [20, 30, 40].

7.4 Tuple methods, and list ⇄ tuple

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))
Watch the bracket shapes. 🤔
Output3 3 [3, 5, 5, 5, 8] (1, 2, 3) [5, 3, 5, 8, 5]
Read the brackets, not the numbers

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.

 listtuple
Written with[ ]( )
MutableYesNo
append / insert / remove / sortYesNo
count, index, len, max, min, sumYesYes
Indexing and slicingYesYes
Usable as a dictionary keyNoYes
Use it fordata that grows or changesa fixed record — a row, a point
The last row is the exam answer to “why would you use a tuple instead of a list?”

7.5 Check yourself

Question 6

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)
Are you ready for the answer? 🤔
Answer
Output(2, 3, 4) (1, 2, 3, 4, 5, 6) (1, 2, 3, 4, 5, 1, 2, 3, 4, 5) True

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).

Challenge

Why would T + 6 fail, when T + (6,) works?

Answer

+ 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.

8.1 When a number is the wrong address

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)
The two amber lines look identical. They are not. 🤔
Output{'name': 'Riya', 'cls': 12, 'marks': 88} Riya 3 {'name': 'Riya', 'cls': 12, 'marks': 91, 'stream': 'Science'}
Definition

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.

One syntax, two jobs

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.

8.2 Asking for a key that isn't there

WRONG — main.py1student = {"name": "Riya", "cls": 12}
2print(student["marks"])
Name the exception. 🤔
TracebackTraceback (most recent call last): File "main.py", line 2, in <module> print(student["marks"]) ~~~~~~~^^^^^^^^^ KeyError: 'marks'
SAFEstudent = {"name": "Riya", "cls": 12}
print(student.get("marks"))
print(student.get("marks", 0))
print("cls" in student)
print("marks" in student)
OutputNone 0 True False
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().

8.3 Dictionary methods

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)
The first three lines print something unusual. 🤔
Outputdict_keys(['a', 'b', 'c']) dict_values([1, 2, 3]) dict_items([('a', 1), ('b', 2), ('c', 3)]) 2 {'a': 1, 'c': 3} {'a': 1, 'c': 30, 'z': 26}
Write it exactly

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()).

Two more things in that output

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.

8.4 Walking a dictionary

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))
Seven lines of output. 🤔
OutputMaths -> 95 Physics -> 78 CS -> 99 Maths 95 Physics 78 CS 99 90.66666666666667
A bare 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.

Copy that last number exactly

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.

8.5 The program you will be asked to write

freq.pytext = "banana"
freq = {}

for ch in text:
    if ch in freq:
        freq[ch] = freq[ch] + 1
    else:
        freq[ch] = 1

print(freq)
What is the dictionary, and in what order? 🤔
chalready a key?freq after
bNo{'b': 1}
aNo{'b': 1, 'a': 1}
nNo{'b': 1, 'a': 1, 'n': 1}
aYes{'b': 1, 'a': 2, 'n': 1}
nYes{'b': 1, 'a': 2, 'n': 2}
aYes{'b': 1, 'a': 3, 'n': 2}
Output{'b': 1, 'a': 3, 'n': 2}
Why the order is b, a, n

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.

Is a dictionary ordered or unordered?

Both answers appear in circulation, so be precise about which one you mean.

Unordered in the sense that matters: you cannot write d[0] to get "the first pair". There is no positional index.
Ordered in the sense that printing or looping gives you insertion order, guaranteed since Python 3.7.

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.

8.6 What may be a key

WRONG — main.py1d = {}
2d[[1, 2]] = "list key"
Will Python accept a list as a key? 🤔
TracebackTraceback (most recent call last): File "main.py", line 2, in <module> d[[1, 2]] = "list key" ~^^^^^^^^ TypeError: unhashable type: 'list'
The rule

A key must be immutableint, float, str or tuple. A value may be anything at all, including a list or another dictionary.

Why the restriction exists

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.

So this is fine
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.

8.7 Check yourself

Question 7

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))
Are you ready for the answer? 🤔
Answer
Output{1: 'ONE', 2: 'two', 3: 'three'} [1, 2, 3] 3

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.

9.1 Which structure, when?

You need to…UseBecause
hold textstrit is characters, in order, and never edited in place
collect marks and keep adding to themlistordered and mutable
hold one fixed record — (name, class, marks)tuplethe shape must not change
look something up by a name or an IDdictaccess by key, not by position
count how often each item occursdictitem → count is exactly key → value
use a pair as a lookup keytuplea list is unhashable; a tuple is not
One question answers most of these

Does this thing need to change after I build it?” Yes → list or dictionary. No → string or tuple.

9.2 The four errors you have now met

ExceptionRaised byMessage you must be able to quote
ValueErrorint("abc")invalid literal for int() with base 10: 'abc'
TypeErrors[0] = "X"'str' object does not support item assignment
TypeErrort[0] = 99'tuple' object does not support item assignment
KeyErrord["missing"]'missing'
TypeErrord[[1, 2]] = xunhashable type: 'list'
Naming the exception is a one-mark question, and it is asked almost every year.
Forward link

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.

9.3 Everything on one screen

Names and objects

object binding, not copying mutable / immutable id() is vs == aliasing a[:] copies

Operators

/ gives a float // floors % follows the divisor's sign ** is right-associative -3 ** 2 = -9 in, not in

Control flow

if / elif / else indentation is syntax range excludes stop break exits the loop continue skips one pass dry-run table

Collections

s[start:stop:step] s[::-1] reverses append vs extend pop returns, remove does not sort() vs sorted() (5,) is the tuple d.get() never raises keys must be immutable d.items() gives tuples

9.4 Exam question 1 — find the output

3 marks

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"))
Are you ready for the answer? 🤔
Answer
Output{'upper': 1, 'lower': 4, 'digits': 4} 6202 9 1

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.

9.5 Exam question 2 — remove the errors

2 marks

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")
Three errors. Find all three. 🤔
Answer
LineErrorCorrection
2Range — Python is case-sensitiverange
3= assigns; a condition needs a comparison==
5missing colon after elseelse:
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")
Output10 even 25 odd 30 even 41 odd

9.6 Exam question 3 — write the program

3 marks

Write a program that counts the vowels, consonants, digits and other characters in the string "Computer Science 2024".

Write it fully before you look. 🤔
Answer
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)
OutputVowels: 6 Consonants: 9 Digits: 4 Others: 2
Where the marks are

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.

9.7 Exam question 4 — write the program

3 marks

Given L = [12, 45, 7, 45, 30, 3], print the largest and the second largest value. Note that 45 occurs twice.

The duplicate is the whole difficulty. 🤔
Answer
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)
OutputLargest = 45 Second largest = 30
The wrong answer that looks right

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.

9.8 Exam question 5 — write the program

3 marks

From words = ["sun", "planet", "moon", "galaxy"], build a dictionary mapping each word to its length, then print the longest word.

Are you ready for the answer? 🤔
Answer
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)
Output{'sun': 3, 'planet': 6, 'moon': 4, 'galaxy': 6} Longest: planet
Why 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.

9.9 One last challenge

Challenge

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)
Are you ready for the answer? 🤔
Answer
Output('Riya', 12, 88) ('Riya', 12, 93) False False

Line 1T 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 3is 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.

End of Chapter 1

Next: Functions

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.

Carry these three forward

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.