1. Python Syntax Whitespace and indentation Python uses whitespace/indentation to structure code instead of {}. A compound statement contains one or
Python Study Guide — Part 2
Types, Casting, datetime, Control Flow, Loops, and Boolean Logic
This guide expands your notes into a detailed, test-focused study guide. I’ll explain not just what each concept does, but why the examples produce their results, since your notes suggest the test may ask you to predict output.
1. Equality vs. Object Identity
This is one of the most important Python concepts in your notes.
There are two different questions you can ask:
Do these objects have the same value?
Are these names pointing to the exact same object?
These are not the same question.
== — Equality
The == operator checks whether two objects have equal values.
a = [1, 2]
b = a
c = list(a)
print(a == b)
print(a == c)
Output:
True
True
Why?
All three lists contain:
[1, 2]
So they have equal values.
2. is — Identity
The is operator asks:
Are these two names referring to the same object?
Consider:
a = [1, 2]
b = a
c = list(a)
b = a creates another binding to the same list.
a ─────┐
↓
[1, 2]
↑
b ─────┘
But:
c = list(a)
creates a new list object.
Conceptually:
a ─────→ [1, 2]
b ─────→ [1, 2]
c ─────→ [1, 2]
The contents are the same, but c is a different object.
Therefore:
print(a is b)
print(a is c)
gives:
True
False
3. == vs. is
Memorize this:
Operator | Question |
|---|---|
| Do they have equal values? |
| Do they have unequal values? |
| Are they the same object? |
| Are they different objects? |
Example
a = [1, 2]
b = [1, 2]
Then:
a == b
is:
True
because the values are equal.
But:
a is b
is:
False
because they are two separate list objects.
4. id()
id() gives an object's identity.
For example:
a = [1, 2]
b = a
c = list(a)
print(id(a))
print(id(b))
print(id(c))
You should expect:
id(a) == id(b)
id(a) != id(c)
because a and b refer to the same object.
Important equivalence
a is b
is conceptually equivalent to:
id(a) == id(b)
Similarly:
a is not b
corresponds to:
id(a) != id(b)
Test question
If:
a = [1, 2]
b = a
c = list(a)
which are true?
a == b # True
a == c # True
a is b # True
a is c # False
id(a) == id(b) # True
id(a) == id(c) # False
5. None
None is Python's standard null/absence-of-value object.
None
Its type is:
type(None)
which gives:
NoneType
Important
None is not the same as:
0
or:
False
or:
""
They are different objects/values.
6. None as a Function Return Value
If a function doesn't explicitly return something, it returns None.
Example:
def hello():
print("Hello")
Calling:
x = hello()
prints:
Hello
but:
x
is:
None
because the function has no return statement.
Example
def add_one(x):
x + 1
This does not return x + 1.
You need:
def add_one(x):
return x + 1
Without return, the result is None.
7. None as a Default Argument
None is frequently used to indicate that an optional argument wasn't provided.
For example:
def greet(name=None):
if name is None:
print("Hello!")
else:
print("Hello", name)
Then:
greet()
uses the default:
None
8. Type Casting
Type casting means converting an object/value to another type.
Common casting functions include:
str()
int()
float()
bool()
list()
9. str()
str() converts an object into a string representation.
str(5)
gives:
"5"
You can also convert more complicated objects:
str([1, 3])
gives something like:
"[1, 3]"
Notice the difference:
[1, 3]
is a list.
While:
"[1, 3]"
is a string.
10. list()
list() can convert an iterable into a list.
For example:
k = "Python"
list(k)
produces:
['P', 'y', 't', 'h', 'o', 'n']
Why?
Because a string is a sequence of Unicode characters.
So:
"Python"
can be thought of as:
P y t h o n
and list() puts those characters into a list.
11. Strings Are Both Scalar and Sequence Types
Your notes make an important observation:
Strings are a scalar type and a sequence type.
For example:
word = "Python"
You can access individual characters:
word[0]
→ "P"
You can slice:
word[1:4]
→ "yth"
And convert to a list:
list(word)
→ ['P', 'y', 't', 'h', 'o', 'n']
12. Common Casting Functions
Know these:
Function | Converts to |
|---|---|
| string |
| integer |
| float |
| Boolean |
| list |
Examples:
int("5")
→ 5
float("5")
→ 5.0
str(5)
→ "5"
bool(1)
→ True
bool(0)
→ False
13. datetime
Python has a built-in datetime module for working with dates and times.
You can import useful classes with:
from datetime import datetime, date, time
14. Creating a datetime
Example:
dt = datetime(2025, 8, 26, 18, 30, 0)
The arguments represent:
year
month
day
hour
minute
second
So this represents:
August 26, 2025 at 6:30:00 PM
15. Getting the Date
Given:
dt = datetime(2025, 8, 26, 18, 30, 0)
you can use:
dt.date()
to get just the date portion.
Conceptually:
2025-08-26
16. Current Date and Time
datetime.now()
returns the current date and time.
For example, it could look like:
2026-09-23 22:38:...
The exact result depends on when the code is run.
17. Formatting Dates with strftime()
strftime() converts a datetime into a formatted string.
Example:
dt.strftime("%m-%d-%Y %H:%M")
Possible result:
08-26-2025 18:30
Important formatting codes
Code | Meaning |
|---|---|
| 4-digit year |
| 2-digit year |
| month as number |
| day |
| hour, 24-hour clock |
| minute |
| second |
| full month name |
| abbreviated month name |
Example:
dt.strftime("%B")
returns:
August
18. strptime() — String → Datetime
This is extremely important.
strptime() parses a string into a datetime object.
Example:
newdt = datetime.strptime("20090131", "%Y%m%d")
The format:
%Y%m%d
means:
4-digit year + 2-digit month + 2-digit day
So:
20090131
means:
January 31, 2009
Memorize:
strptime = string → datetime
strftime = datetime → string
A useful mnemonic:
P = Parse →
strptime
F = Format →
strftime
19. timedelta
When you subtract two datetime objects:
datetime.now() - newdt
Python produces a timedelta object.
Example:
duration = datetime.now() - newdt
Then:
type(duration)
will be:
datetime.timedelta
A timedelta represents a duration of time.
For example:
5000 days, 3:42:10
This is particularly useful when working with time-series data.
20. strftime vs. strptime
This is a likely test question.
strftime
Datetime → String
dt.strftime("%Y-%m-%d")
strptime
String → Datetime
datetime.strptime("2025-08-26", "%Y-%m-%d")
Remember
strftime = format
strptime = parse
21. Control Flow
Control flow determines which statements execute and when.
The major control-flow statements in your notes are:
ifforwhile
22. if Statements
Basic structure:
if condition:
statement
Example:
if x > 5:
print("Large")
The part:
if x > 5:
is the header.
The indented code:
print("Large")
is the body/suite.
23. if / else
if condition:
# runs if condition is True
else:
# runs if condition is False
Example:
if x > 5:
print("Large")
else:
print("Small")
This is a multi-clause statement.
24. if / elif / else
You can have multiple conditions:
if x > 10:
print("large")
elif x > 5:
print("medium")
else:
print("small")
Python evaluates the conditions from top to bottom and executes the appropriate branch.
25. for Loops
A for loop repeats code for each item in an iterable.
Example:
for char in "Python":
print(char)
Output:
P
y
t
h
o
n
26. Combining for and if
Your notes use:
vowels = {"a", "e", "i", "o", "u"}
for i in "Python":
if i in vowels:
print(f"vowel: {i}")
else:
print(f"consonant: {i}")
Let's break it down.
Step 1
for i in "Python":
Each character is assigned to i.
Step 2
if i in vowels:
Checks whether the character is a member of the vowels set.
Step 3
If true:
print(f"vowel: {i}")
Otherwise:
print(f"consonant: {i}")
27. f-Strings
This syntax:
f"vowel: {i}"
is an f-string.
It allows you to insert variables into strings.
Example:
name = "Sam"
age = 20
print(f"My name is {name} and I am {age}.")
Output:
My name is Sam and I am 20.
Remember:
f"...{variable}..."
28. Conditions Can Be Compound
A condition doesn't have to be simple.
Example:
if x > 5 and y < 10:
print("Yes")
Multiple logical conditions can be combined with:
and
or
not
29. Short-Circuit Evaluation
Python evaluates Boolean expressions from left to right and can stop early.
and
For:
A and B
if A is false, Python doesn't need to evaluate B.
Example:
False and something
The result is already determined.
or
For:
A or B
if A is true, Python doesn't need to evaluate B.
Example:
True or something
The result is already determined.
This is called short-circuit evaluation.
30. break
break immediately exits the loop.
Example:
for i in range(10):
if i == 5:
break
print(i)
Output:
0
1
2
3
4
When i becomes 5, break exits the loop.
Remember
break= leave the loop
31. continue
continue does something different.
It skips the rest of the current iteration and moves to the next iteration.
Example:
for i in range(5):
if i == 2:
continue
print(i)
Output:
0
1
3
4
The loop doesn't end.
It simply skips 2.
Remember
continue= skip this iteration
break= stop the loop
32. break vs. continue
Keyword | What happens? |
|---|---|
| Completely exits the loop |
| Skips to the next iteration |
| Does nothing |
This distinction is very testable.
33. pass
pass means:
Do nothing.
Example:
if x > 5:
pass
This is syntactically valid even though nothing happens.
Why is pass useful?
Python uses indentation to determine where clauses end, so sometimes you need a statement in a block even when you don't want that block to do anything yet.
Example:
for x in values:
if x < 0:
pass
34. range()
range() produces a sequence of integers.
The common forms are:
range(stop)
range(start, stop)
range(start, stop, step)
range(stop)
range(5)
represents:
0, 1, 2, 3, 4
Notice that 5 is excluded.
range(start, stop)
range(2, 6)
represents:
2, 3, 4, 5
range(start, stop, step)
range(1, 10, 2)
represents:
1, 3, 5, 7, 9
The stop value is still excluded.
35. Example: Every Fourth Letter
Your notes use:
word = "abcdefghijklmnopqrstuvwxyz"
[word[i] for i in range(1, len(word), 4)]
Let's break it down.
len(word)
is:
26
So:
range(1, 26, 4)
produces:
1, 5, 9, 13, 17, 21, 25
Those are the indices selected from the string.
Because Python starts indexing at 0:
a = 0
b = 1
c = 2
...
z = 25
Therefore the result is:
['b', 'f', 'j', 'n', 'r', 'v', 'z']
36. List Comprehensions
This:
[word[i] for i in range(1, len(word), 4)]
is a list comprehension.
General structure:
[expression for variable in iterable]
Example:
[x * 2 for x in range(5)]
produces:
[0, 2, 4, 6, 8]
You can think of it as a compact way to build a list using a loop.
37. Boolean Expressions — Very Important
Your Boolean quiz contains some concepts that are extremely important because Python's and and or do something slightly different from what beginners often expect.
Python's and and or don't necessarily return True or False.
They can return one of their operands.
38. or
Consider:
True or 2
Result:
True
Because the first operand is truthy, Python stops.
But:
2 or True
returns:
2
Why?
2 is truthy, so Python doesn't need to evaluate the second operand.
Key rule
For:
A or B
if A is truthy, the result is A.
Otherwise, the result is B.
39. and
For:
A and B
if A is falsy, the result is A.
Otherwise, the result is B.
Example:
3 and 1
returns:
1
because 3 is truthy, so Python evaluates and returns the second operand.
40. Why bool() Matters
Remember:
bool(2)
is:
True
But:
2 == True
is:
False
These are different operations.
Why?
bool(2) asks:
What is the truth value of
2?
Since any nonzero integer is truthy:
True
But:
2 == True
asks:
Is the value
2equal to the valueTrue?
In Python:
True == 1
is True, but:
2 == True
is False.
41. True and False Behave Like 1 and 0
In numerical comparisons:
True == 1
→ True
and:
False == 0
→ True
But don't confuse this with truthiness.
For example:
bool(2)
is True, but:
2 == True
is False.
42. Understanding the Boolean Quiz
Let's work through each one.
Expression | Result | Why |
|---|---|---|
|
| First operand is truthy |
|
|
|
|
| Nonzero numbers are truthy |
|
|
|
|
|
|
|
| First part is false, so return second operand |
|
|
|
|
| First part true, so return |
|
| Operator precedence makes this a bitwise expression |
|
|
|
43. The Tricky One: 2 < 1 or 3
Start with:
2 < 1
which is:
False
So:
False or 3
returns:
3
It does not return True.
This illustrates that or returns an operand.
44. The Tricky One: 3 and 1 < 2
Operator precedence means the comparison happens:
1 < 2
→ True
So:
3 and True
Since 3 is truthy:
True
45. The Tricky One: 1 < 2 and 3
First:
1 < 2
→ True
Then:
True and 3
returns:
3
Important
and doesn't necessarily return a Boolean.
46. The Tricky One: 2 or not 3
First:
not 3
would be:
False
But Python doesn't even need to evaluate it because:
2
is already truthy.
So:
2 or not 3
returns:
2
This is short-circuit evaluation.
47. The Very Tricky One: 1 < 2 & 3
This is different from:
1 < 2 and 3
because & is a bitwise operator, not Boolean and.
Operator precedence means the expression is evaluated in terms of the bitwise operation before the comparison.
Conceptually:
2 & 3
is:
2
because:
2 = 10
3 = 11
--
10
Then:
1 < 2
is:
True
So the result is:
True
Test warning
Don't replace:
and
with:
&
They are not interchangeable.
48. Operator Precedence
Operator precedence determines which operations happen first.
A simplified hierarchy useful for this material is:
parentheses
↓
arithmetic
↓
bitwise operations
↓
comparisons
↓
not
↓
and
↓
or
The exact full Python precedence table is more detailed, but the key point from your notes is:
Comparisons happen before Boolean
and/or, while bitwise operators have different precedence.
When uncertain, use parentheses.
For example:
(1 < 2) and 3
is much easier to understand than relying on precedence.
49. while Loops
Your notes mention while as another major control-flow structure.
A while loop continues while its condition is truthy.
x = 0
while x < 5:
print(x)
x += 1
Output:
0
1
2
3
4
Structure
while condition:
body
Like if and for, it uses:
Header
Colon
Indented suite/body
50. Control Flow Summary
Statement | Purpose |
|---|---|
| Execute code conditionally |
| Check another condition |
| Execute when previous conditions are false |
| Iterate over a sequence/iterable |
| Repeat while condition is truthy |
| Exit loop |
| Skip current iteration |
| Do nothing |
51. Key Vocabulary to Know
Your professor uses specific terminology. Be able to recognize these terms.
Object
A value/entity in Python with a type, attributes, and methods.
Attribute
Data associated with an object.
Accessed with:
object.attribute
Method
A function associated with an object.
Example:
list.sort()
Namespace
A mapping between names and objects.
Binding
The relationship between a name and an object.
Mutable
Can be changed in place.
Immutable
Cannot be changed in place.
Scope
Where a name is visible.
Casting
Converting an object/value to another type.
Control flow
Determines which statements execute and in what order.
Short-circuit evaluation
Python stops evaluating a Boolean expression once its result is already determined.
⭐ Highest-Priority Test Concepts
If you're studying the night before the exam, focus heavily on these.
Tier 1 — Absolutely know
==vs.isid()Mutable vs. immutable
Assignment/binding
Nonetype()isinstance()str(),int(),float(),bool(),list()strftime()vs.strptime()if,for,whilebreakvs.continuevs.passrange(start, stop, step)Boolean short-circuiting
andvs.orand/orcan return operands rather thanTrue/Falseandvs.&
🧠 Must-Know Examples
Be able to predict these without running Python.
Example 1
a = [1, 2]
b = a
c = list(a)
print(a == b)
print(a == c)
print(a is b)
print(a is c)
Answer:
True
True
True
False
Example 2
a = [1, 2]
b = a
a.append(3)
print(b)
Answer:
[1, 2, 3]
Because a and b refer to the same mutable list.
Example 3
a = 5
b = a
a = 6
print(b)
Answer:
5
Because a = 6 binds a to a different integer object. It doesn't modify the original integer.
Example 4
def f():
print("Hello")
x = f()
print(x)
Answer:
Hello
None
Example 5
print(2 or True)
Answer:
2
Example 6
print(1 < 2 and 3)
Answer:
3
Example 7
print(2 == True)
print(1 == True)
Answer:
False
True
Example 8
for i in range(2, 8, 2):
print(i)
Answer:
2
4
6
Example 9
for i in range(5):
if i == 2:
continue
print(i)
Answer:
0
1
3
4
Example 10
for i in range(5):
if i == 2:
break
print(i)
Answer:
0
1
🔥 Final Memorization Sheet
== equal values
!= unequal values
is same object
is not different objects
id(x) identity of x
None Python's null/absence-of-value object
NoneType type(None)
str() → string
int() → integer
float() → float
bool() → Boolean
list() → list
strftime datetime → string
strptime string → datetime
timedelta duration between datetime objects
if conditional execution
for iteration
while repeated execution while condition is true
break leave loop
continue skip current iteration
pass do nothing
range(5) 0,1,2,3,4
range(2,5) 2,3,4
range(1,10,2) 1,3,5,7,9
and Boolean AND; may return an operand
or Boolean OR; may return an operand
not logical negation
& bitwise AND
| bitwise OR
^ bitwise XOR
~ bitwise complement
True == 1
False == 0
bool(2) True
2 == True False
"string" immutable
list mutable
The Big Picture
The most important conceptual chain in this section is:
Names → objects → types → identity/value → mutability → operations → control flow.
If you understand that Python variables are names bound to objects, then == vs. is, id(), mutable vs. immutable objects, function arguments, and many of the Boolean/control-flow examples become much easier to reason about.
Python Native Types — Detailed Study Guide
1. Big Picture: Python Collection Types
Before getting into each type, memorize this comparison:
Type | Syntax | Ordered/Sequence? | Mutable? | Duplicates? | Access by index? |
|---|---|---|---|---|---|
Tuple |
| Yes | ❌ No | Yes | Yes |
List |
| Yes | ✅ Yes | Yes | Yes |
Dictionary |
| Mapping | ✅ Yes | Keys unique | By key |
Set |
| Unordered | ✅ Yes | ❌ No | ❌ No |
Quick memory trick
Tuple = list that can't be changed
List = changeable sequence
Dictionary = key → value
Set = unique collection
2. Tuples
A tuple is:
A fixed-length, immutable sequence that can contain objects of different types.
Example:
a = ([1, 2], "A+", {"grade": "F-"})
Notice that the tuple contains three different objects:
[1, 2]
"A+"
{"grade": "F-"}
The objects don't have to be the same type.
3. Creating Tuples
Tuples can be written with parentheses:
b = ([1, 2], "A+", {"grade": "F-"})
But parentheses aren't actually required.
a = [1, 2], "A+", {"grade": "F-"}
Both create tuples.
Important
The commas are what make the tuple.
For example:
x = (5)
is just an integer:
type(x)
# int
But:
x = (5,)
is a tuple:
type(x)
# tuple
Test question
What is the difference?
(5)
vs.
(5,)
Answer:
(5)→int(5,)→tuple
4. Tuple Immutability
Tuples cannot be modified directly.
For example:
a = (1, 2, 3)
You cannot do:
a[0] = 10
That produces an error because tuples are immutable.
5. But Tuples Can Contain Mutable Objects
This is a very important subtlety.
Consider:
a = ([1, 2], "A+", {"grade": "F-"})
The tuple itself cannot be changed.
But the dictionary inside the tuple is mutable.
Therefore:
a[2]["grade"] = "C+"
is allowed.
The tuple still contains the same dictionary object, but the dictionary itself has changed.
Before:
(
[1, 2],
"A+",
{"grade": "F-"}
)
After:
(
[1, 2],
"A+",
{"grade": "C+"}
)
Key distinction
The tuple is immutable, but objects contained inside the tuple might be mutable.
6. Tuple Methods and Operations
Tuples support many sequence operations.
Concatenation
a + b
combines two tuples into a new tuple.
Example:
a = (1, 2)
b = (3, 4)
a + b
Result:
(1, 2, 3, 4)
Repetition
You can use *:
b * 2
If:
b = (3, 4)
then:
b * 2
produces:
(3, 4, 3, 4)
7. Tuple Unpacking
You can assign the individual elements of a tuple to variables.
a = (10, 20, 30)
x, y, z = a
Now:
x = 10
y = 20
z = 30
This is called tuple unpacking.
Important
The number of variables normally has to match the number of values.
x, y = (1, 2)
works.
But:
x, y = (1, 2, 3)
causes an error because there are too many values.
8. Unpacking in a for Loop
You can unpack tuples while iterating.
Example:
seq = [(1, 2, 3), (4, 5, 6)]
for c, d, e in seq:
print(f"c = {c}, d = {d}, e = {e}")
First iteration:
c = 1
d = 2
e = 3
Second:
c = 4
d = 5
e = 6
Python automatically unpacks each tuple.
9. *args and **kwargs
You may see these in function definitions and documentation.
The important idea:
*
Used for variable-length positional arguments.
**
Used for variable-length keyword arguments.
For example:
def f(*args):
print(args)
Calling:
f(1, 2, 3)
makes args a tuple:
(1, 2, 3)
10. Extended Tuple Unpacking
You can use * when unpacking.
Example:
a = (1, 2, 3)
p, *args = a
Result:
p = 1
args = [2, 3]
Important detail
The *args variable receives the remaining values as a list in an unpacking assignment.
So:
p, *args = (1, 2, 3)
gives:
p == 1
args == [2, 3]
11. Lists
A list is:
A variable-length, mutable sequence that can contain heterogeneous objects.
Example:
a = [[1, 2], "A+", {"grade": "F-"}]
Lists use square brackets:
[1, 2, 3]
12. Lists Are Mutable
Unlike tuples, lists can be changed.
a = [1, 2, 3]
a[0] = 100
Now:
a
is:
[100, 2, 3]
13. list()
You can create a list using list().
For example:
b = list((3, 4, "VSCode", {"grade": "C+"}))
This converts the tuple into a list.
Result:
[3, 4, "VSCode", {"grade": "C+"}]
14. list() and Iterators
list() is also useful for materializing an iterator.
For example:
gen = range(10)
gen represents a range of numbers.
list(gen)
produces:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Vocabulary
Materialize means essentially:
Turn the iterable/iterator's values into an actual collection that you can see and work with.
15. .insert()
The insert() method adds an item at a specific index.
Syntax:
list.insert(index, value)
Example:
a = [1, 2, 3]
a.insert(1, "hello")
Result:
[1, "hello", 2, 3]
Important
The existing elements are shifted over.
16. .pop()
pop() removes an item by index and returns the removed item.
Example:
b = [1, 2, 3, 4]
x = b.pop(2)
Now:
x
is:
3
and:
b
is:
[1, 2, 4]
Remember
pop → position/index
17. .append()
append() adds one object to the end of a list.
a = [1, 2]
a.append(3)
Result:
[1, 2, 3]
Important
If you append a list:
a.append([4, 5])
you get:
[1, 2, 3, [4, 5]]
The entire list [4, 5] is added as one item.
18. .extend()
extend() adds multiple items to the end of a list.
a = [1, 2]
a.extend([3, 4])
Result:
[1, 2, 3, 4]
append() vs. extend()
This is highly testable.
a = [1, 2]
a.append([3, 4])
→
[1, 2, [3, 4]]
But:
a = [1, 2]
a.extend([3, 4])
→
[1, 2, 3, 4]
Memory trick
append = add one thing
extend = add the contents of another iterable
19. .remove()
remove() removes an item based on its value, not its index.
a = [1, 2, 3, 2]
a.remove(2)
Result:
[1, 3, 2]
Only the first matching value is removed.
Compare:
pop(2)
means:
Remove the item at index 2.
While:
remove(2)
means:
Find the value
2and remove its first occurrence.
20. List Concatenation: + vs .extend()
These accomplish similar-looking tasks differently.
+
a = [1, 2]
b = [3, 4]
c = a + b
Creates a new list.
a → [1, 2]
b → [3, 4]
c → [1, 2, 3, 4]
a remains unchanged.
.extend()
a.extend(b)
modifies a itself.
Now:
a
is:
[1, 2, 3, 4]
Important
+creates a new list.
.extend()modifies the existing list.
21. Membership in Lists
You can check whether an item exists:
3 in [1, 2, 3]
→ True
Or:
4 not in [1, 2, 3]
→ True
Operators:
in
not in
22. Lists vs. Sets for Searching
Your notes mention efficiency.
Lists generally require searching through elements one by one.
Sets use hash tables, which generally provide much faster membership testing.
Therefore, if you're frequently asking:
x in collection
a set can be much more efficient when the data can appropriately be represented as a set.
Important conceptual distinction
Use a:
list when sequence/order and duplicates matter
set when uniqueness and fast membership testing matter
23. .sort()
A list can be sorted in place.
Example:
a = ["stat", "I", "love", "mathematics"]
a.sort()
The list itself changes.
Important
.sort() modifies the original list.
It does not create a separate sorted list.
24. .sort(key=len)
You can specify how Python should sort the elements.
a.sort(key=len)
This means:
Sort the strings according to their length.
For:
["stat", "I", "love", "mathematics"]
the lengths are:
stat → 4
I → 1
love → 4
mathematics → 11
So the result will be ordered by length.
25. Slicing
One of the most important list/sequence concepts:
[start:stop:step]
Key rule
Start is included; stop is excluded.
Example:
a = ["a", "b", "c", "d", "e"]
a[1:4]
gives:
["b", "c", "d"]
Index 4 is not included.
26. Basic Slicing
a[2:3]
Start at index 2, stop before 3.
a[2:3]
returns one element.
a[:2]
Means:
Start at the beginning, stop before index 2.
a[:2]
a[2:]
Means:
Start at index 2 and go to the end.
a[:-1]
Means:
Start at the beginning and stop before the last element.
a[-1:]
Means:
Start at the last element and go through the end.
So this produces a one-element list containing the last item.
a[::2]
Means:
Take every second element.
27. Negative Indexing
Python allows negative indexes.
For:
a = ["a", "b", "c", "d"]
the indexes are:
a b c d
↑ ↑ ↑ ↑
0 1 2 3
-4 -3 -2 -1
Therefore:
a[-1]
is:
"d"
and:
a[-2]
is:
"c"
28. Dictionary
A dictionary is a mutable collection of key-value pairs.
Syntax:
{
key: value,
key: value
}
Example:
a = {
"name": "Sam",
"grade": "A"
}
Conceptually:
"name" → "Sam"
"grade" → "A"
29. Empty Dictionary
This:
a = {}
creates an empty dictionary.
Important!
It does not create an empty set.
An empty set is:
set()
This is a common test question.
{} → empty dictionary
set() → empty set
30. Creating Dictionaries with dict()
You can construct a dictionary from pairs:
dict(
(("first", 1),
("second", 2),
("third", 3))
)
Result:
{
"first": 1,
"second": 2,
"third": 3
}
31. Dictionary Access
Use the key to access the value.
a["third"]
If:
a = {
"first": 1,
"second": 2,
"third": 3
}
then:
a["third"]
returns:
3
32. Adding or Modifying Dictionary Values
You can assign directly:
a["fourth"] = 4
If "fourth" doesn't exist, it is added.
If it already exists, its value is changed.
Example:
a["first"] = 100
changes the existing value.
33. .update()
You can add or modify several key-value pairs at once.
a.update({
"first": 1,
"second": 2,
"third": 3
})
34. .pop() for Dictionaries
Dictionary .pop() removes a key and returns its value.
a.pop("third")
If:
a["third"] = 3
then:
a.pop("third")
returns:
3
and removes "third" from the dictionary.
Compare list and dictionary pop()
List:
a.pop(2)
→ removes by index
Dictionary:
a.pop("third")
→ removes by key
35. del with Dictionaries
You can delete a key-value pair:
del a["fourth"]
The "fourth" key and its value are removed.
36. list(dictionary)
When you convert a dictionary to a list:
list(a)
Python returns a list of the keys.
Example:
a = {
"first": 1,
"second": 2
}
Then:
list(a)
produces:
["first", "second"]
Important
It does not return the values.
37. sorted(dictionary)
When you use:
sorted(a)
Python sorts the dictionary's keys.
The result is a list.
Example:
a = {
"z": 1,
"a": 2,
"m": 3
}
Then:
sorted(a)
produces:
["a", "m", "z"]
38. .keys()
Returns an iterable/view of the dictionary's keys.
a.keys()
You can iterate over it:
for key in a.keys():
print(key)
39. .values()
Returns the dictionary's values.
a.values()
Example:
a = {
"a": 10,
"b": 20
}
The values are:
10
20
40. .items()
Returns key-value pairs.
a.items()
You can unpack them:
for key, value in a.items():
print(key, value)
This is extremely common Python syntax.
41. Dictionary Summary
Memorize:
dict.keys() → keys
dict.values() → values
dict.items() → (key, value) pairs
And:
list(dict) → keys
sorted(dict) → sorted keys
42. Dictionary Keys Must Be Hashable
Dictionary values can be almost any object.
But keys must be hashable.
Examples of hashable objects:
"Python"
5
3.14
(1, 2)
Lists are not hashable:
[1, 2]
so you cannot use a list as a dictionary key.
43. What Does "Hashable" Mean?
A hashable object can produce a stable hash value.
You can test an object with:
hash("Python")
If Python can hash it, it can generally be used as a dictionary key or set element.
Common rule
Immutable objects are often hashable.
Mutable objects such as lists and dictionaries are not hashable.
44. zip()
zip() combines multiple iterables element-by-element.
Example:
names = ["A", "B", "C"]
numbers = [1, 2, 3]
zip(names, numbers)
Conceptually produces:
("A", 1)
("B", 2)
("C", 3)
To see the results:
list(zip(names, numbers))
gives:
[("A", 1), ("B", 2), ("C", 3)]
45. zip() and Dictionaries
Your notes use:
v = list("Python")
k = range(len(v))
dict(zip(k, v))
Let's break it down.
v
is:
["P", "y", "t", "h", "o", "n"]
and:
k
represents:
0, 1, 2, 3, 4, 5
zip(k, v) pairs them:
0 → P
1 → y
2 → t
3 → h
4 → o
5 → n
Then dict() creates:
{
0: "P",
1: "y",
2: "t",
3: "h",
4: "o",
5: "n"
}
46. zip() Stops at the Shortest Iterable
This is very important.
Suppose:
a = [1, 2, 3, 4]
b = ["a", "b"]
Then:
list(zip(a, b))
produces:
[(1, "a"), (2, "b")]
The extra 3 and 4 are ignored.
Rule
zip()stops when the shortest input runs out of values.
47. enumerate()
enumerate() is extremely useful when you need both:
the index
the value
Instead of:
for i in range(len(my_data)):
print(i, my_data[i])
you can write:
for index, value in enumerate(my_data):
print(index, value)
Example:
my_data = [1, 2, 3, 4]
produces pairs conceptually:
(0, 1)
(1, 2)
(2, 3)
(3, 4)
48. enumerate() vs. zip()
enumerate()
Adds an index:
enumerate(["a", "b", "c"])
→
(0, "a")
(1, "b")
(2, "c")
zip()
Combines multiple sequences:
zip([1, 2], ["a", "b"])
→
(1, "a")
(2, "b")
49. reversed()
reversed() allows you to iterate through an iterable in reverse order.
Example:
x = [1, 2, 3]
list(reversed(x))
returns:
[3, 2, 1]
50. Iterators
An iterator produces values one at a time.
Functions such as:
zip()
enumerate()
reversed()
can produce iterator-like objects.
For example:
rev_obj = reversed([1, 2, 3])
You may not immediately see the actual values when printing rev_obj.
You can materialize them:
list(rev_obj)
→
[3, 2, 1]
51. Important: Iterators Can Be Consumed
If an object is an iterator, its values are generally produced as you iterate through it.
For example:
z = zip([1, 2], ["a", "b"])
list(z)
produces:
[(1, "a"), (2, "b")]
If you then do:
list(z)
again, you may get:
[]
because the iterator has already been consumed.
Key idea
An iterator produces values as you consume it.
52. Sets
A set is an unordered collection of unique elements.
Example:
{1, 2, 3}
53. Sets Automatically Remove Duplicates
Consider:
set([2, 2, 2, 1, 3, 3])
The duplicates disappear.
Result conceptually:
{1, 2, 3}
The exact display order should not be relied upon.
54. Sets Are Unordered
You cannot use an index:
my_set[0]
This is invalid.
A set doesn't have the sequence-style indexing of lists and tuples.
Remember
list → indexed
tuple → indexed
set → not indexed
dict → accessed by key
55. Sets Require Hashable Elements
Like dictionary keys, set elements must be hashable.
This works:
{1, 2, 3}
This doesn't:
{[1, 2, 3]}
because lists are mutable and unhashable.
56. Putting a List into a Set
If you need a list-like object as a set element, convert it to a tuple:
my_data = [1, 2, 3, 4]
my_set = {tuple(my_data)}
Now:
my_set
contains:
{(1, 2, 3, 4)}
because tuples can be hashable.
57. Set Operations
Sets support mathematical operations such as:
Union
Intersection
Difference
Suppose:
A = {1, 2, 3}
B = {3, 4, 5}
Union
Everything in either set:
A | B
→
{1, 2, 3, 4, 5}
Intersection
Elements in both:
A & B
→
{3}
Difference
Elements in A but not B:
A - B
→
{1, 2}
58. Set Methods vs. Operators
You can use methods:
A.union(B)
or operators:
A | B
Important difference
The method form can accept certain non-set iterables by converting them appropriately.
The binary operator form expects another set.
Your notes demonstrate:
my_set.union(my_data)
versus:
my_set | my_data
The first can work when my_data is a list; the second requires set operands.
59. In-Place Set Operations
You can combine operation + assignment.
For example:
my_set |= more_set
is essentially:
my_set = my_set | more_set
It updates the set with the union.
Similar operators include:
&=
|=
^=
-=
60. Set Equality
Set equality works based on the elements, not ordering.
For example:
{1, 2, 3} == {3, 2, 1}
is:
True
because they contain the same elements.
This is different from sequences where order matters.
61. List vs. Tuple vs. Set vs. Dictionary
This comparison is worth memorizing:
Feature | List | Tuple | Set | Dictionary |
|---|---|---|---|---|
Mutable | ✅ | ❌ | ✅ | ✅ |
Ordered/sequence | ✅ | ✅ | ❌ | Mapping |
Duplicates | ✅ | ✅ | ❌ | Keys ❌ |
Indexing | ✅ | ✅ | ❌ | Key access |
Syntax |
|
|
|
|
Main purpose | Changeable sequence | Fixed sequence | Unique elements | Key/value mapping |
62. Comprehensions
A comprehension is a concise way of creating a collection.
Instead of:
result = []
for value in collection:
if condition:
result.append(expr)
you can write:
[expr for value in collection if condition]
63. List Comprehension
Example:
strings = ["a", "as", "bat", "car", "dove", "python"]
[x.upper() for x in strings if len(x) > 3]
Let's break it down:
x.upper()
is what we put into the resulting list.
for x in strings
means iterate through each string.
if len(x) > 3
means only keep strings longer than 3 characters.
Result:
["BATS"?]
More precisely, the qualifying words are:
"bat" → length 3 → excluded
"car" → length 3 → excluded
"dove" → length 4 → included
"python" → length 6 → included
So the result is:
["DOVE", "PYTHON"]
64. General Comprehension Pattern
Memorize:
[expression for variable in collection if condition]
Read it almost like English:
Put
expressioninto the list for everyvariableincollectionifconditionis true.
65. Comprehensions Without an if
You don't need a condition.
[x * 2 for x in range(5)]
Result:
[0, 2, 4, 6, 8]
66. Set Comprehensions
You can use curly braces:
{x * 2 for x in range(5)}
This creates a set.
Result:
{0, 2, 4, 6, 8}
Remember that sets automatically remove duplicates.
67. Dictionary Comprehensions
Dictionary comprehensions use:
{key: value for ...}
Example:
{x: x**2 for x in range(4)}
produces:
{
0: 0,
1: 1,
2: 4,
3: 9
}
68. Nested Comprehensions
This is one of the trickier topics.
Suppose:
two_lists = [
["John", "Emily", "Michael", "Mary", "Steven"],
["Maria", "Juan", "Javier", "Natalia", "Pilar"]
]
The comprehension:
[name.lower()
for namelist in two_lists
for name in namelist
if len(name) > 5]
works like nested loops.
Equivalent expanded version:
result = []
for namelist in two_lists:
for name in namelist:
if len(name) > 5:
result.append(name.lower())
69. How to Read Nested Comprehensions
Read from the outside inward, matching nested loops.
[
name.lower()
for namelist in two_lists
for name in namelist
if len(name) > 5
]
Think:
for each namelist
for each name
if name is longer than 5
add lowercase name
The result is:
[
"michael",
"steven",
"maria",
"javier",
"natalia"
]
70. Flattening a List of Lists
Suppose:
two_lists = [
["John", "Emily", "Michael"],
["Maria", "Juan", "Javier"]
]
You want:
[
"John",
"Emily",
"Michael",
"Maria",
"Juan",
"Javier"
]
You can use:
[name
for namelist in two_lists
for name in namelist]
This is called flattening the nested lists.
71. map()
map() applies a function to every element.
Your notes use:
list(map(lambda x: x[-1], strings))
The important idea is:
Apply the function to every element.
For example:
numbers = [1, 2, 3]
list(map(lambda x: x * 2, numbers))
produces:
[2, 4, 6]
72. map() vs. Comprehension
These can often accomplish similar things.
Using map():
list(map(lambda x: x * 2, numbers))
Using a comprehension:
[x * 2 for x in numbers]
Both produce:
[2, 4, 6]
Your notes emphasize comprehensions because they are often very readable for this kind of operation.
73. lambda
A lambda is a small anonymous function.
Example:
lambda x: x * 2
means roughly:
def f(x):
return x * 2
Then:
list(map(lambda x: x * 2, [1, 2, 3]))
gives:
[2, 4, 6]
⭐ Most Important Methods to Memorize
Lists
Method | What it does |
|---|---|
| Add one item to end |
| Add multiple items |
| Insert at index |
| Remove/return item at index |
| Remove first matching value |
| Sort list in place |
Biggest traps
append([1, 2])
adds one list.
extend([1, 2])
adds two elements.
pop(2)
uses an index.
remove(2)
uses a value.
⭐ Dictionary Methods
Method | Meaning |
|---|---|
| keys |
| values |
| key-value pairs |
| add/change multiple pairs |
| remove key and return value |
⭐ Set Operations
A | B # union
A & B # intersection
A - B # difference
A ^ B # symmetric difference
And methods:
A.union(B)
A.intersection(B)
A.difference(B)
A.symmetric_difference(B)
⭐ Functions You Need to Know
Function | Purpose |
|---|---|
| Convert to list |
| Convert to tuple |
| Convert to set |
| Create/convert dictionary |
| Combine iterables |
| Produce index + value |
| Iterate backward |
| Return a new sorted list |
| Get hash value if hashable |
| Apply function to elements |
🚨 sort() vs. sorted()
This is another very important distinction.
.sort()
Only works on lists and modifies the list:
a = [3, 1, 2]
a.sort()
Now:
a
is:
[1, 2, 3]
sorted()
Returns a new sorted list:
a = [3, 1, 2]
b = sorted(a)
Now:
a = [3, 1, 2]
b = [1, 2, 3]
Memorize
.sort()→ changes the list
sorted()→ creates/returns a sorted list
🧠 High-Priority Test Traps
Trap 1: Empty {}
{}
is:
dictionary, not set.
Empty set:
set()
Trap 2: Tuple parentheses
(5)
is an integer.
(5,)
is a tuple.
Trap 3: Tuple immutability
A tuple can't be modified:
t[0] = 5
But a mutable object inside the tuple can be modified:
t[1].append(5)
Trap 4: append() vs. extend()
a = [1, 2]
a.append([3, 4])
→
[1, 2, [3, 4]]
while:
a = [1, 2]
a.extend([3, 4])
→
[1, 2, 3, 4]
Trap 5: pop() vs. remove()
a.pop(2)
→ remove index 2.
a.remove(2)
→ remove first value equal to 2.
Trap 6: list(dictionary)
list(my_dict)
→ keys, not values.
Trap 7: Dictionary keys
Dictionary keys must be hashable.
{[1, 2]: "hello"}
doesn't work because lists aren't hashable.
But:
{(1, 2): "hello"}
can work because tuples can be hashable.
Trap 8: Set duplicates
set([1, 1, 2, 2, 3])
becomes:
{1, 2, 3}
Trap 9: Set indexing
This doesn't work:
my_set[0]
Sets are not indexed sequences.
Trap 10: zip() length
zip([1, 2, 3], ["a", "b"])
only produces two pairs.
It stops at the shortest iterable.
Trap 11: Iterator materialization
z = zip([1, 2], ["a", "b"])
list(z)
shows the values.
But after consuming the iterator, calling list(z) again may produce an empty list
Function Details
Rules
A function is declared with the keyword
deffollowed by the function signature and terminated with a colon.{python style=""}
#| echo: true
def my_func():
print("Hello")
my_func()It's optional to use a
returnstatement to return control back to the calling context.{python style=""}
#| echo: true
def new_func(x, y):
return x + y
new_func(4, 5)In the absence of a
returnstatement, a function returnsNone.{python style=""}
#| echo: true
def weird_func(x, y):
x + y
weird_func(4, 5)A function can have both "positional" and "keyword" arguments.
Positional arguments are required, keyword arguments are optional, and all keyword arguments must follow all positional arguments.
When invoking a function, keyword arguments do not need to be specified with the keyword, but it is good practice to do so.
There's nothing prohibiting multiple
returnstatements.{python style=""}
#| echo: true
def test_func(y, x = 5):
x *= y
return x
return x + y
test_func(4)This is commonly seen in multi-clause
ifstatements{python style=""}
#| echo: true
def test_func(y, x = 5):
if x < y:
x *= y
return x
elif x > y:
return x
test_func(4)
Namespace
A function can access any variable defined within the function ("local scope") or any higher scope, including the "global scope". For example, in the function call
test_func(4), variablesxandyare created in the local namespace and assigned to5and4, respectively.After
return xis encountered, control is returned to the calling space and the local namespace is destroyed. Therefore,xandyare destroyed. The secondreturnstatement isn't executed.If a variable is defined in a higher scope, it can be referenced and mutated within a function:
{python style=""}
#| echo: true
a = []
def prod_func():
for i in range(5):
# Modify a
a.append(i)
prod_func()
a{python style=""}
#| echo: true
x = 1
def prod_func(y):
# Reference x
y *= x
prod_func(3)However, you higher scope variable cannot be assigned/reassigned a value:
{python style=""}
#| echo: true
try:
x = 1
def prod_func(y):
# Assign to x
x *= y
prod_func(3)
except Exception as e:
# store exception object in 'e'
print(f"An unexpected error occurred: {e}")There is a way to assign/reassign a value to a variable outside of the local scope using the keywords
globalornonlocal; however, this is discouraged.
Function-alikes
Lambdas
Lambda expressions are "anonymous functions" consisting of a single expression that is also the return value. They're defined using the keyword
lambda.{python style=""}
#| echo: true
def incrementor(n):
return lambda x: x + n
f = incrementor(5)
f(4)Lambdas are most often used when you need to pass a short function as an argument to another function. For example,
{python style=""}
#| echo: true
b = list((3, 4, "VSCode", {"grade": "C+"}))
list(map(lambda x: type(x), b)){python style=""}
#| echo: true
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key = lambda x: x[1])
pairs
Generators & Iterators
Python uses an "iterator protocol" to make objects iterable. For example, when you loop over keys in a dictionary:
{python style=""}
#| echo: true
some_dict = {"a": 1, "b": 2, "c": 3}
for key in some_dict:
key.upper()the Python interpretor makes an
iteratorthat accesses the elements in the dictionary container one item at a time:{python style=""}
#| echo: true
iter(some_dict)Most methods that accept a list or list-like object will also accept an iterator:
{python style=""}
#| echo: true
list(iter(some_dict))A
generatorlooks and feels like a function, but is specifically designed to create iterators.On the surface, one difference between a function and generator is that instead of keyword
return, a generator uses keywordyield.{python style=""}
#| echo: true
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]You can use
next()to step through the generator. The generator remembers the data values and which statement was last executed.{python style=""}
#| echo: true
# Instantiate the reverse generator
rev = reverse('Python')
next(rev)
next(rev)
next(rev)Just like list, dictionary, or set comprehensions, there is a shorthand for
generator expressions:{python style=""}
#| echo: true
# Define & Instantiate the reverse generator
rev2 = ('Python'[i] for i in range(len('Python') - 1, -1, -1))
next(rev2)
next(rev2)
Output of the Snippet
Evaluating the final array division (data / 20) yields:
Python
array([[ 0.075 , -0.005 , 0.15 ],
[ 0. , -0.15 , 0.325 ]])
Core Concepts Explained
1. Why NumPy Outperforms Standard Python Lists
Feature | Python Lists | NumPy ndarray |
Memory Allocation | Array of pointers to generic Python objects scattered in memory. | Contiguous block of homogeneous data in memory. |
Type Checking | Dynamically checks data type for every single element on every operation. | Single uniform type ( |
Execution | Interpreted Python bytecode loop. | Compiled C code executing vector hardware instructions (SIMD). |
Contiguous Memory: Storing elements sequentially in RAM maximizes CPU cache hits and allows modern CPUs to perform SIMD (Single Instruction, Multiple Data) operations, processing multiple numbers in a single CPU cycle.
No Interpreter Overhead: Operations like
my_arr * 2pass memory addresses directly to C loops, bypassing Python object creation and reference counting for each element.
2. Vectorization and Element-Wise Operations
In pure Python, transforming an array requires a loop or list comprehension:
Python
my_list2 = [x * 2 for x in my_list]
With NumPy, operations are vectorized and apply element-wise automatically across the entire array structure (including 2D matrices like data / 20):
Python
my_arr2 = my_arr * 2
This scalar operation is broadcast across all elements of the array without explicit Python loops.
3. IPython Magic Commands (%timeit)
%timeitis an IPython line magic command (prefixed with%).It automatically runs a statement thousands of times to compute an accurate mean and standard deviation execution time.
On a element array, NumPy is typically to faster than standard Python list comprehensions.