Session 3: Python Data Types and Program Flow Control
Date: 21-03-2026 4:00 pm to 6:00 pm
Agenda
- Review of previous sessions
- Answers to queries
- Python has a dynamic type system
- Some operations on
list - Some operations on
str range()to generate a sequence of integers- Program flow control -
forloop - Program flow control -
whileloop - Program flow control -
if - elif - else - Function signatures
- Designing functions
Python has a dynamic type system
Programming languages can be classified as statically typed or dynamically typed.
- In a statically typed language, the data type of a variable must be specified when it is created and cannot change during program execution.
- In a dynamically typed language, the type of a variable is determined automatically and can change during execution.
Python is a dynamically typed language. You do not need to declare the type of a variable explicitly. Instead, the type is inferred from the value assigned to it. If a new value of a different type is assigned later, the variable’s type changes accordingly.
Example
>>> a = [10, 2.5, "Hello", 2 > 3]
>>> a
[10, 2.5, 'Hello', False]
>>> x = a[0]
>>> print(x, type(x))
10 <class 'int'>
>>> x = a[1]
>>> print(x, type(x))
2.5 <class 'float'>
>>> x = a[2]
>>> print(x, type(x))
Hello <class 'str'>
Note
- Data type of
xis determined by the values assigned to it - Data type of
xis not static. It changes when the type of the value assigned to it is different.
Dynamic typing makes Python flexible and easy to use, since types do not need to be declared explicitly. However, in larger programs, it requires care to ensure that values have the expected types.
To address this, Python supports optional type annotations. These allow developers to specify expected types (especially for function parameters), helping editors and tools detect potential issues. However, the Python interpreter does not enforce type annotations at runtime.
Some operations on list
A list in Python is:
- Mutable (its elements can be changed)
- A container (it can store multiple elements)
List elements:
- Can be of different types
- Can include built-in or user-defined types
- Can include other lists (nested lists)
Unlike arrays in some languages, Python lists:
- Do not require elements to be of the same type
- Do not require uniform structure (nested lists can vary in size)
Example: mixed data types
>>> a = [10, 2.5, 'Hello, world', [1, 2.0]]
>>> len(a)
4
>>> type(a[0])
<class 'int'>
>>> type(a[1])
<class 'float'>
>>> type(a[2])
<class 'str'>
>>> type(a[3])
<class 'list'>
>>> len(a[3])
2
>>> type(a[3][0])
<class 'int'>
>>> type(a[3][1])
<class 'float'>
Notes
- Elements of
ahave different types a[3]is itself a list- Nested elements can be accessed using indices (e.g.,
a[3][0]) - Nested lists can be viewed as multi-dimensional, but they do not need to be uniform
Modifying list elements
Since lists are mutable, their elements can be changed:
>>> a[0] = 20
>>> a
[20, 2.5, 'Hello, world', [1, 2.0]]
>>> a[3][0] = 10
>>> a
[20, 2.5, 'Hello, world', [10, 2.0]]
Appending elements
>>> a.append(50)
>>> a
[20, 2.5, 'Hello, world', [10, 2.0], 50]
>>> a.append((10, 20, 30))
>>> a
[20, 2.5, 'Hello, world', [10, 2.0], 50, (10, 20, 30)]
Notes
append()adds an element to the end of the list- The list can store values of any type, including tuples
Inserting elements
Behavior of list.insert(i, element)
- If
i = 0→ insert at the beginning - If
i = len(a)→ equivalent toappend() - If
i > len(a)→ element is appended - If
i < 0→ position is adjusted using:
List methods
| Method | Description |
|---|---|
list.pop([i]) |
Remove and return element at index i (default: last element) |
list.clear() |
Remove all elements |
list.remove(x) |
Remove first occurrence of x |
list.count(x) |
Count occurrences of x |
list.index(x[, start[, end]]) |
Return index of first occurrence of x |
list.sort(*, key=None, reverse=False) |
Sort list in place |
list.reverse() |
Reverse list in place |
list.copy() |
Return a shallow copy |
Note
Square brackets in method signatures (e.g., pop([i])) indicate optional arguments, not literal syntax.
For more information, see:
More on lists
Examples: removing elements
>>> a = [1, 2, 3, 4, 5]
>>> a.pop(0)
1
>>> a
[2, 3, 4, 5]
>>> a.pop()
5
>>> a
[2, 3, 4]
>>> a.pop(10)
IndexError: pop index out of range
>>> a.clear()
>>> a
[]
Notes
pop()returns the removed value- Invalid indices raise
IndexError
More examples
>>> a = [1, 2, 3, 4, 5, 4]
>>> a.remove(4)
>>> a
[1, 2, 3, 5, 4]
>>> a.append(5)
>>> a
[1, 2, 3, 5, 4, 5]
>>> a.count(2)
1
>>> a.count(5)
2
>>> a.index(5)
3
>>> a.index(5, 4)
5
>>> a.index(6)
ValueError: list.index(x): x not in list
Notes
remove()deletes only the first occurrenceindex()raisesValueErrorif the element is not found- The returned index is always relative to the full list
Sorting and copying
>>> a = [10, 7, 2, 4, 6, 7]
>>> a.sort()
>>> a
[2, 4, 6, 7, 7, 10]
>>> a.reverse()
>>> a
[10, 7, 7, 6, 4, 2]
>>> x = [1, 2, 3]
>>> y = x
>>> z = x.copy()
>>> x is y
True
>>> x is z
False
Notes
sort()modifies the list in placereverse()modifies the list in placey = xcreates a reference to the same objectcopy()creates a new list with the same elements
Some operations on str
A Python str provides many useful methods for processing text.
Common string methods
| Method | Description |
|---|---|
str.capitalize() |
Return a copy of the string with the first character capitalized and the rest lowercased |
str.casefold() |
Return a casefolded copy of the string (more aggressive than lower(), especially for non-English characters) |
str.center(width[, fillchar]) |
Return the string centered in a field of given width, padded with fillchar (default: space) |
str.count(sub[, start[, end]]) |
Return the number of non-overlapping occurrences of sub |
str.endswith(suffix[, start[, end]]) |
Return True if the string ends with the specified suffix |
str.find(sub[, start[, end]]) |
Return the lowest index where sub is found, or -1 if not found |
str.lower() |
Return a lowercase copy of the string |
str.upper() |
Return an uppercase copy of the string |
For a complete list, see:
Text Sequence Type - str
Examples
>>> a = 'abracadabra'
>>> b = a.capitalize()
>>> b
'Abracadabra'
>>> c = b.upper()
>>> c
'ABRACADABRA'
>>> c.lower()
'abracadabra'
>>> a = "this is a sentence with several words"
>>> a.title()
'This Is A Sentence With Several Words'
>>> a.center(60)
' this is a sentence with several words '
>>> a.center(60, '-')
'-----------this is a sentence with several words------------'
>>> a.startswith('thi')
True
>>> a.endswith("words")
True
>>> a.replace(" ", "_")
'this_is_a_sentence_with_several_words'
>>> x = " hello, world "
>>> x.strip()
'hello, world'
>>> x.lstrip()
'hello, world '
>>> x.rstrip()
' hello, world'
>>> y = "This is just the beginning."
>>> y.rstrip(".")
'This is just the beginning'
Joining strings
>>> a = ["apple", "orange", "banana", "grapes"]
>>> " ".join(a)
'apple orange banana grapes'
>>> ", ".join(a)
'apple, orange, banana, grapes'
Notes
join()combines elements of an iterable into a single string- The string on which
join()is called is used as the separator
There are many more str methods, and they are very useful for text processing. Refer to the Python documentation for a complete list.
range() to generate a sequence of integers
Let us explore range() using the Python REPL.
>>> range(5)
range(0, 5)
>>> range(1, 6)
range(1, 6)
>>> range(1, 10, 2)
range(1, 10, 2)
>>> r = range(5)
>>> print(r.start, r.stop, r.step)
0 5 1
The range() function returns an object of type <class 'range'>.
>>> type(range(5))
<class 'range'>
>>> print(range(5))
range(0, 5)
>>> print(range(1, 10, 2))
range(1, 10, 2)
>>> r = range(1, 10, 2)
>>> print(r.start, r.stop, r.step)
1 10 2
>>> list(r)
[1, 3, 5, 7, 9]
Notes
- A
rangeobject has three attributes:start,stop, andstep - It generates values one at a time, rather than storing all values in memory
- You can convert it to a
listortupleto generate all values at once rangeis a sequence type, so it supports indexing and slicing
Examples
>>> len(range(5))
5
>>> len(range(1, 10, 2))
5
>>> 3 in range(5)
True
>>> 4 in range(1, 10, 2)
False
>>> range(1, 6)[3]
4
>>> range(1, 6)[-1]
5
Program flow control - for loop
The for loop iterates over elements of an iterable, such as a list, tuple, or string.
>>> lst = ["one", "two", "three"]
>>> for item in lst: # Pythonic
... print(item)
...
one
two
three
>>> for i in range(len(lst)): # Less Pythonic
... print(lst[i])
...
one
two
three
>>> for i in range(len(lst)):
... print(i)
...
0
1
2
>>> for i in range(1, 10, 2):
... print(i, end=" ")
...
1 3 5 7 9 >>>
>>> for i in range(10, 1, -2):
... print(i, end=", ")
...
10, 8, 6, 4, 2, >>>
Handling conditions inside loops
>>> a = [10, 20, 0, 5]
>>> for x in a:
... print(100 / x)
...
10.0
5.0
Traceback (most recent call last):
File "<python-input-91>", line 2, in <module>
print(100 / x)
ZeroDivisionError: division by zero
>>> for x in a:
... if x == 0:
... continue
... print(100 / x)
...
10.0
5.0
20.0
Notes
- The
forloop works directly with elements of an iterable - Using
for item in lstis generally more readable than indexing withrange(len(lst)) - The
continuestatement skips the remaining part of the current iteration and proceeds to the next one continueis typically used with a conditional statement
Program flow control - while loop
The while loop repeatedly executes a block of code as long as a given condition evaluates to True. The loop stops when the condition becomes False.
A while loop has two important requirements:
- Variables used in the condition must be initialized before the loop starts
- These variables must be updated inside the loop so that the condition eventually becomes
False
If these conditions are not met:
- The loop may never execute
- The loop may run indefinitely (infinite loop)
The block of code executed in each iteration is defined by indentation.
Example
The following example simulates a for i in range(5) loop:
Using a while loop in place of a for loop in such cases is generally less efficient and less readable. A while loop is more appropriate when:
- The number of iterations is not known in advance
- The stopping condition is complex
Example: Iterative computation
>>> maxiter = 10
>>> x = 5
>>> i = 0
>>> guess = x / 2
>>> while (abs(guess * guess - x) > 0.0001) and (i < maxiter):
... guess = (guess + x / guess) / 2
... i += 1
...
>>> print(i, guess)
3 2.2360679779158037
Notes
-
The loop continues while the condition
abs(guess * guess - x) > 0.0001 and i < maxiter
remainsTrue -
The number of iterations depends on:
- the value of
x -
the chosen tolerance (
0.0001) -
Variables initialized before the loop:
maxiter = 10i = 0-
guess = x / 2 -
The goal of the loop is to approximate the square root of
x
Program flow control - if - elif - else
Conditional branching in Python is performed using the if - elif - else construct.
- The
ifclause is required - The
elifandelseclauses are optional
Dual branching (if - else)
Notes
- A colon (
:) is required at the end of eachif,elif, andelsestatement - The condition can be a simple or complex logical expression
- Indentation is mandatory and must be consistent throughout the code