Chapter 2: Python Fundamentals#

Exploring Built-in Functions, String Manipulation,
Numeric Operations, and Data Structures#

Author: Dr. Suborna Ahmed

Learning Objectives:#

  • Builtin Functions in Python:

    • String:

      • Common String Operators

      • Slicing String Variable

    • Numeric

      • Arithmetic

      • Assignment

      • Comparison

      • Logical Operators

  • Basic Data Structures

  • Complex Data Structures

    • Lists

    • Tuples

    • Dictionaries

Built-in Functions in Python#

Many methods are pre-defined in the Python standard library and cover a wide range of operations. We will learn some basic functions to perform common tasks such as manipulating strings, converting data types, working with mathematical calculations, etc. and to work with basic data types in Python. Understanding and utilizing built-in functions will enhance your productivity and enable you to write efficient and concise code.

  • The following links are additional sources where you can find more helpful built-in functions.

1. Built-in Functions in Python for Strings#

We will explore common string operators that allow you to concatenate, repeat, find substrings, and format strings. Understanding how to slice a string variable will enable you to extract specific portions of text based on index positions.

1.1 Common String Operators#

  • string.join(x): returns a string concatenated with the elements of x.

  • string.find(sub): returns the first index where the substring sub is found such that sub is wholly contained between the start and end.

  • string.lower(s): returns a copy of s, but with upper case letters converted to lower case.

  • string.upper(s): returns a copy of s, but with lower case letters converted to upper case.

  • string.replace(old, new, number): returns a copy of string with all occurrences of substring old replaced by new. Here number is optional which represents how many times it will replace.

  • In the first example below: the join() method is used on the string y with the argument x. When join() is called on a string, it concatenates the elements of an iterable (in this case, the string x) with the string on which the method is called (in this case, the string y). It inserts the string y between each element of x.

  • In the second example: the join() method is used on the string k1 with the argument k2. Since join() expects an iterable as its argument, it treats the string k2 as a sequence of characters. In this case, k1.join(k2) will result in the string “oSUBLsUBnUBa”, as the string k1 is inserted between each character of k2.

# string.join()
x = "123"
y = "-"
print(y.join(x))

k1 = 'SUB'
k2 = "orna"
print(k1.join(k2))
1-2-3
oSUBrSUBnSUBa

Note

index starts at 0 in Python !

In Python, an index refers to the position of an element within a sequence, such as a list, string, or tuple. It allows us to access and manipulate individual elements based on their position.
Python uses zero-based indexing, meaning the first element in a sequence is assigned an index of 0, the second element has an index of 1, and so on.

# string.find(sub)
weather = "Windy Weather"
n_index = weather.find("n")
print("n_index: ", n_index) # locates the position (index) of the letter "n" for the first time
t_index = weather.find("t")
print("t_index: ", t_index) # locates the position (index) of the letter "t" for the first time
n_index:  2
t_index:  9

The code below explains how to count the index in Python. You don’t need to understand the code for `Loop`` yet, we will discuss it in Week3.

string = "Windy Weather"

for index, letter in enumerate(string):
    print(f"The letter '{letter}' is at index {index}.")
The letter 'W' is at index 0.
The letter 'i' is at index 1.
The letter 'n' is at index 2.
The letter 'd' is at index 3.
The letter 'y' is at index 4.
The letter ' ' is at index 5.
The letter 'W' is at index 6.
The letter 'e' is at index 7.
The letter 'a' is at index 8.
The letter 't' is at index 9.
The letter 'h' is at index 10.
The letter 'e' is at index 11.
The letter 'r' is at index 12.
# string.lower(): convert all characters to lower case
# string.upper(): convert all characters to upper case

k1 = 'Sub'
k2 = "orna"

print(k1.lower())

print(k2.upper())

k=k1+k2
print(k)
sub
ORNA
Suborna

Activity 1

Investigate string.replace(old, new, number) and write a code with the provided exa string variable below.

exa = "this is an example"
# hint: to get help on Jupyter, you can type the function and with a question mark after it and run the code.
# write codes here

See solution for activity 1 at the end.

1.2 Slicing String Variable#

In Python, slicing can be performed on strings, allowing you to extract substrings or specific portions of a string. Slicing a string allows you to create a new string that contains only the selected characters. The resulting substring is a new string object and does not affect the original string.

Slicing a string follows the same general syntax as slicing any sequence in Python: string[start:end:step].

  • When slicing a string, the character at the start index is included in the slice, but the character at the end index is not. It means that string[start:end] extracts characters from start up to, but not including, the character at end.

  • Indices can be specified using positive or negative numbers. Positive indices count from the beginning of the string (starting from 0), while negative indices count from the end of the string (starting from -1).

  • The step value determines the spacing between characters in the slice. A positive step value moves forward through the string, while a negative step value moves backward. For example, a step of 2 would select every second character in the slice.

  • Default Values: If you omit the start index, it defaults to the beginning of the string. If you omit the end index, it defaults to the end of the string. If you omit the step value, it defaults to 1.

# investigate the outputs for the following example
p = 'Hamilton'
print(len(p)) # count the length of characters in the string
8
# access the character at index 0 in the string p, corresponds to the first character of the string.
print(p[0]) 
H
# extracts a substring from the string p, starting from index 0 up to, but not including, index 2
# the first and second character of the string are extracted
print(p[0:2])
Ha
# negative indices count from the end of the string,
# accesses the character at index -5 in the string p. 
print(p[-5])
i

Except String, we can also use slicing with List.

  • Index starts at 0 !

my_list = [0, 10, 20, 30, 40, 50]

print(my_list[1:4])   # slicing from index 1 to 4(exclusive)
print(my_list[:3])    # slicing starts from the beginning, index 0, to index 3(exlusive)
print(my_list[2:])    # slicing starts from index 2 (inclusive) to the end of the list 
print(my_list[::2])   # slicing skips every second element in the list
print(my_list[::-1])  # negative step value of -1 indicating a reverse order.
[10, 20, 30]
[0, 10, 20]
[20, 30, 40, 50]
[0, 20, 40]
[50, 40, 30, 20, 10, 0]

Activity 2

What would be the output? Create a Markdown chunk under this one and explain the output:

  • s = ‘GEM 530’

  • s[-2]

  • s[3]

See solution for activity 2 at the end.

2. Built-in Functions in Python for Numerics#

The numeric section focuses on arithmetic, assignment, comparison, and logical operators in Python. We will learn how to perform mathematical calculations using operators such as addition (+), subtraction (-), multiplication (*), division (/), and exponentiation (**). Understanding how to assign values to variables efficiently, compare values and evaluate conditions, and manipulate logical conditions with logical operators.

2.1 Arithmetic Operators#

Arithmetic operators are used to perform mathematical calculations in Python. In week 1, we have seen some examples for using basic math calculator in Python, including: addition (+), subtraction (-), multiplication (*), division (/), and exponentiation (**). Now we will introduce two more commonly used arithmetic operators:

  • Modulus (%): Returns the remainder of the division of the left operand by the right operand.

  • Floor Division (//): Returns the result of the division, rounded down to the nearest whole number.

For the following example, let’s say, a = 5 & b = 2.

Arithmetic Operators

Description

Result

+

Addition

\(a + b = 7\)

-

Subtraction

\(a - b = 3\)

*

Multiplication

\(a \times b = 10\)

/

Division

\(a \div b = 2.5\)

**

Exponentiation:
performs exponential (power) calculation on operatos

\(a ^ b = 25\)

%

Modulus:
divides left hand operand by right hand operand and returns remainder

a \(\%\) b = 1

//

Floor Division:
the division of operands where the result is the quotient in which the digits after the decimal point are removed

9//2 is equal to 4,
9.0//2.0 is equal to 4.0

a = 5
b = 2

print("a + b = ", a+b)
print("a - b = ", a-b)
print("a * b = ", a*b)
print("a / b = ", a/b)
print("a ** b = ", a**b)
print("a % b = ", a%b)
print("a // b = ", a//b)
a + b =  7
a - b =  3
a * b =  10
a / b =  2.5
a ** b =  25
a % b =  1
a // b =  2
  • In Python 2, when dividing integers, the result will be an integer.

  • In Python 3, the result includes decimals.

5/2 # in Python 2 it will give 2 only, while Python 3 will generate 2.5
2.5

2.2 Assignment Operators#

Assignment operators are used to assign values to variables. Here are the commonly used assignment operators:

  • = (Assign): Assigns the value on the right to the variable on the left.

  • += (Add and Assign): Adds the value on the right to the variable on the left and assigns the result to the variable.

  • -= (Subtract and Assign): Subtracts the value on the right from the variable on the left and assigns the result to the variable.

  • *= (Multiply and Assign): Multiplies the variable on the left by the value on the right and assigns the result to the variable.

  • /= (Divide and Assign): Divides the variable on the left by the value on the right and assigns the result to the variable.

Note

=(Assign) and ==(Equals) are different operators in Python.

Assignment Operator (=):

The assignment operator is used to assign the value on the right-hand side to the variable on the left-hand side. The assignment operator is a fundamental part of variable initialization and value assignment in Python.

Example: x = 5 assigns the value 5 to the variable x.

Equality Operator (==):

The equality operator is used to compare if two values on both sides of the operator are equal or not. It returns a Boolean value (True or False) based on the comparison result.

Example: x == 5 compares the value of x to 5 and returns True if they are equal, and False otherwise.

For the following example, let’s say, a = 5 & b = 2.

Arithmetic Operators

Name

Example

Meaning

Result of a

=

Simple assignment

a = b

Set a as b

2

+=

Add AND

a += b

a = a + b

7

-=

Subtract AND

a -= b

a = a - b

3

*=

Multiply AND

a *= b

a = a * b

10

/=

Divide AND

a /= b

a = a / b

2.5

%=

Modulus AND

a %= b

a = a % b

1

**=

Exponent AND

a **= b

a = a ** b

25

= after the operator: +, -, *, /, … means a is reassigned

# Assignment Operators
print('#operation', 'a', 'b')

# 1. 
a = 5
b = 2
a = b # the value of 'b' is assigned to 'a' using the assignment operator (=). 
print("#1: a=b", a, b)

# 2. 
a = 5
b = 2
a += b # adds the value of 'b' to the current value of 'a' and assigns the result back to 'a'
print("#2: a+=b", a, b)

# 3. 
a = 5
b = 2
a -= b # subtracts the value of 'b' from the current value of 'a' and assigns the result back to 'a'
print("#3: a-=b",a, b)

# 4.
a = 5
b = 2
a *= b # multiplies the current value of 'a' by the value of 'b' and assigns the result back to 'a'
print("#4: a*=b", a, b)

# 5.
a = 5
b = 2
a /= b # divides the current value of 'a' by the value of 'b' and assigns the result back to 'a' 
print("#5: a/=b", a, b)

# 6.
a = 5
b = 2
a %= b # calculates the remainder when 'a' is divided by 'b' and assigns the result back to 'a'
print("#6: a%=b", a, b)

# 7.
a = 5
b = 2
a **= b # raises 'a' to the power of 'b' and assigns the result back to 'a'
print("#7: a**=b", a, b)
#operation a b
#1: a=b 2 2
#2: a+=b 7 2
#3: a-=b 3 2
#4: a*=b 10 2
#5: a/=b 2.5 2
#6: a%=b 1 2
#7: a**=b 25 2

2.3 Comparison Operators#

Comparison operators are used to compare the values of variables. They return Boolean values, either True or False. Here are the commonly used comparison operators:

  • \(==\) (Equal to): Returns True if the operands are equal.

  • \(!=\) (Not equal to): Returns True if the operands are not equal.

  • \(<\) (Less than): Returns True if the left operand is less than the right operand.

  • \(>\) (Greater than): Returns True if the left operand is greater than the right operand.

  • \(<=\) (Less than or equal to): Returns True if the left operand is less than or equal to the right operand.

  • \(>=\) (Greater than or equal to): Returns True if the left operand is greater than or equal to the right operand.

For the following example, let’s say, a = 5 & b = 2.
The output is Boolean Type: TRUE or FALSE.

Comparison Operators

How to use

Dscriptions

Results

==

a == b

a equals b

(a == b) is not true

!=

a != b

a is not equal to b

(a != b) is true

<

a < b

a is less than b

(a < b) is not true

>

a > b

a is greater than b

(a > b) is true

<=

a <= b

a is less than or equal to b

(a <= b) is not true

>=

a >= b

a is greater than or equal to b

(a >= b) is true

is

a is b

a and b are the same object

(a is b) is not true

is not

a is not b

a and b are different objects

(a is not b) is true

in

a in range(b)

a is a member of [0,1,2,…,b-1]

(a in range(b)) is not true

not in

a not in range(b)

a is not a member of [0,1,2,…,b-1]

(a not in range(b)) is true

# Comparison Operators
# 1. 
a = 5
b = 2
a == b # the equality operator (==) is used to compare if the values of 'a' and 'b' equals
print("#1: a == b is", a == b)

# 2. 
a = 5
b = 2
a < b
print("#2: a < b is", a < b)

# 3. 
a = 5
b = 2
a > b
print("#3: a > b is" , a  > b)

# 4.
a = 5
b = 2
a >= b
print("#4: a >= b is", a >= b)

# 5.
a = 5
b = 2
a <= b
print("#5: a <= b is", a <= b)

# 6.
a = 5
b = 2
a !=  b
print("#6: a !=  b is", a !=  b)

# 7.
a = 5
b = 2
a is b
print("#7: a is b is", a is b)

# 8.
a = 5
b = 2
a is not b
print("#8: a is not b is", a is not b)
#1: a == b is False
#2: a < b is False
#3: a > b is True
#4: a >= b is True
#5: a <= b is False
#6: a !=  b is True
#7: a is b is False
#8: a is not b is True

Note

range and list functions

  • The range() function is used to generate a sequence of numbers. In the code below, range(a) creates a range object with a sequence of numbers from 0 to 4 (excluding 5), and range(b) creates a range object with a sequence of numbers from 0 to 1 (excluding 2).

  • The list() function is used to convert the range objects into lists for printing. The list(range(a)) converts the range object range(a) into a list [0, 1, 2, 3, 4], and list(range(b)) converts the range object range(b) into a list [0, 1].

# 9.
a = 5
b = 2
print("list all the members in range(a): ", list(range(a)))
print("list all the members in range(b): ", list(range(b)))

a in range(b)
print("#9:a in range(b) =", a in range(b))
print("#9:b in range(a) =", b in range(a))
list all the members in range(a):  [0, 1, 2, 3, 4]
list all the members in range(b):  [0, 1]
#9:a in range(b) = False
#9:b in range(a) = True

Activity 3

What would be the output?

  • a = 2

  • b = 4

  • a not in range(b)

See solution for activity 3 at the end.

2.4 Logical Operators#

Logical operators are used to manipulate and evaluate logical conditions. They return Boolean results. Here are the commonly used logical operators:

  • and: Returns True if both operands are True.

  • or: Returns True if at least one of the operands is True.

  • not: Returns the opposite Boolean value of the operand.

For the following example, let’s say, a = 5 & b = 2.

Logical Operators

How to use

Results

and

Logical AND operator.
If both the operands are true / non-zero then the condition becomes true.

(a and b) is true

or

Logical OR operator.
If any of the two operands are true / non-zero then the condition becomes true.

(a or b) is true

not

called Logical NOT operator.
Use to reverses the logical state of its operand. If a condition is true then Logical NOT operator will make false.

not(a and b) is false

# Operators: Logical operators
a = 5
b = 2

O1 = (a > 3) and (b < 10) # a > 3 is True, b < 10 is also True, True and True produces True
print("(a > 3) and (b < 10) is ", O1)

O2 = not((a > 1) and (b < 10)) # a > 1 is True, b < 10 is also True, not True produces False
print("not((a > 1) and (b < 10))is ", O2)

O3 = (a > 5) or (b < 10) # a > 5 is False, b < 10 is True
print("(a > 5) or (b < 10) is ", O3) # if at least one of the conditions is true, the expression produces True

O4 = (a > 5) or (b > 10) # a > 5 is False, b > 10 is False
print("(a > 3) or (b < 10) is ", O4) # the expression produces False since both conditions are false

O5 = (a > 10) and (b < 10) # a > 10 is False and b < 10 is True
print("(a > 10) and (b < 10)", O5) # False and True produces False
print("not((a > 10) and (b < 10)) is ", not(O5)) # not(False) produces True
(a > 3) and (b < 10) is 
 True
not((a > 1) and (b < 10))is  False
(a > 5) or (b < 10) is  True
(a > 3) or (b < 10) is  False
(a > 10) and (b < 10) False
not((a > 10) and (b < 10)) is  True
  • Logical operators also can work with Boolean values and return Boolean results.

x = True
y = False
# Output: x and y is False
print('x and y is', x and y)
# Output: x or y is True
print('x or y is', x or y)
# Output: not x is False
print('not x is', not x)
x and y is False
x or y is True
not x is False

Activity 4

What would be the output?

  • True and False or True

  • (True or False) and False

See solution for activity 4 at the end.

3. One More Note on Basic Data Types#

  • Boolean: True or False; Conditional expression results are either True or False.

  • type(): use type() to interrogate an object.

  • len(): will return the length of characters in a string, number entries in a list, tuple, dictionary (more later) etc..

# Basic Data Types
x = type(True)
print(x)

x = type(1)
print(x)
<class 'bool'>
<class 'int'>
# len() function
x = "Hello"
print(len(x))

names= ("aa", "ac") # a tuple named names is defined with two elements: "aa" and "ac".
print("names length = ", len(names))

nn = (1, 2, 3)# a tuple named nn is defined with three elements: 1, 2, and 3. 
print("nn length:", len(nn))
5
names length =  2
nn length: 3

4. Complex Data Structures#

In Python we have some built-in data structures:

  1. List
    Lists are ordered collections that can store heterogeneous data types. A sinlge list can contain different types of data, and even a list can be contained in another list. Each item in the list is separated by a comma and the entire list is enclosed in square brackets [.]

  2. Tuple
    Tuples are similar to lists but immutable. Thus, tuples are useful for data which should not be modified.

  3. Dictionary
    Dictionaries are key-value pairs that allow efficient retrieval of data based on keys.

By mastering these basic data structures, you will have a solid foundation for working with collections of data in Python.

Container

Description

Feature

Examples

list

Delimited by [ ]

mutable

[‘a’, ‘b’, ‘c’]

tuple

Denoted by parenthesis ( )

immutable

(‘a’, ‘b’, ‘c’)

dictionary

{key1: value1,
key2: value2,
…}

mutable (but not entries)

{ ‘Country’: ‘Canada’,
’population’: 38,781,291,
‘provinces’: [‘AB’, ‘BC’, ‘QC’, ‘ON’] }

Composite Type In those data structures, items are:

  • Ordered and sequential.

  • Accessible via an index.

  • Easy for expressing complex operations in a single statement.

4.1 Lists#

In Python we frequently use lists, and there are many methods associated with lists. It primarily works as a holder.

  • We use a list() function or directly use a square bracket [ ] to create a list.

  • Mutable: can change after creating it.

  • Operate by order/number.

  • Index starts at 0.

  • Can be sliced just like a string.

# To create a list

# Option 1: use a list() function
list1 = list([1, 2, 3])
print("list 1:", list1)

print("type of list1: ", type(list1))

# Option 2: directly use a square bracket [ ]
list2 = [4, 5, 6, 8, 9]

print("list 2:", list2)


list3 = list(['a', 'bunch', 'of', 'things'])
print("list 3:", list3)


# A single list can contain different types of data: int, float, string, and another list.
varName = [1, 2, 'a', 4.8, 'b', ['another', 'list', 'within a list'], 6]
print("varName:", varName)
list 1: [1, 2, 3]
type of list1:  <class 'list'>
list 2: [4, 5, 6, 8, 9]
list 3: ['a', 'bunch', 'of', 'things']
varName: [1, 2, 'a', 4.8, 'b', ['another', 'list', 'within a list'], 6]
# Slicing
print("list2[1]:", list2[1])

print("list2[:4]", list2[:4])
list2[1]: 5
list2[:4] [4, 5, 6, 8]
# example to show list is mutable

mutable_list = [1, 2, 3, 4, 5]
print("Original list:", mutable_list)


# the element at index 2, the third element, of the mutable_list is modified by assigning the value 10 to it
mutable_list[2] = 10
print("Updated list:", mutable_list)
Original list: [1, 2, 3, 4, 5]
Updated list: [1, 2, 10, 4, 5]

4.2 Tuples#

A tuple is an immutable collection of items.

  • Tuples are declared with parentheses ( ).

  • Declared with parenthesis () or without a parenthesis.

  • Immutable: cannot change after creating it.

  • Index starts at 0.

  • Also accessed through slicing.

# Create tuples

# Option 1: With parentheses()
myTuple = ("hi", "class")
print(type(myTuple))
print("myTuple:", myTuple)

# Option 2: Without parentheses()
x = 1, 3, 5
print("Type of x:", type(x))
print("tuple x: ", x)
<class 'tuple'>
myTuple: 
('hi', 'class')
Type of x: <class 'tuple'>
tuple x:  (1, 3, 5)
# Slicing
myTuple = ("hi", "class", "welcome", "to", "GEM", "530")
print(myTuple)
print(myTuple[0])
print(myTuple[2:])
('hi', 'class', 'welcome', 'to', 'GEM', '530')
hi
('welcome', 'to', 'GEM', '530')
# Nested tuple: lists are contained in a tuple
my_tuple = ("mouse", [8, 4, 6], (1, 2, 3))
print("my_tuple:", my_tuple)
my_tuple: ('mouse', [8, 4, 6], (1, 2, 3))
# example to show tuple is immutable
my_tuple = (1, 2, 3)
print("Original tuple:", my_tuple)

# we try to change the element at index 1, the second element, in the tuple
# the error message shows we cannot change elements in tuple
my_tuple[1] = 10
print("Updated tuple:", my_tuple) # an Error is expected when run this code
Original tuple: (1, 2, 3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/var/folders/qt/qy37fy6s3c1553y2k74063pr0000gn/T/ipykernel_49154/4002316466.py in <module>
      5 # we try to change the element at index 1, the second element, in the tuple
      6 # the error message shows we cannot change elements in tuple
----> 7 my_tuple[1] = 10
      8 print("Updated tuple:", my_tuple) # an Error is expected when run this code

TypeError: 'tuple' object does not support item assignment

4.3 Dictionaries#

In dictionaries we add sequence of items. We need to add a key and a value in for each item.

  • A dictionary is declared with a curley bracket { } or dict() funcrion.

  • Dictionaries operate using key-value pairs.

  • Accessed through calling the key.

# Create a dictionary with key-value pairs

# Option 1:  with a curley bracket{ }
tel = {'jack': 4098, 'sape': 4139}
print("tel:", tel)

# Option 2:  with the dict() function
my_dict = dict(year=2023, course="GEM530", section="101")
print("my_dict:", my_dict)
tel: {'jack': 4098, 'sape': 4139}
my_dict: {'year': 2023, 'course': 'GEM530', 'section': '101'}
  • Add a new item in the dictionary: dic[new_key] = new_value

  • Find the value for corresponding key: dic[key1] can produce the value corresponding to the key1

  • Change the value for a specific key: dic[old_key] = new_value can update / overwrite the old-value to the new value we want to pair with the old key

# Add another item in the dictionary
tel['guido'] = 4127
print('tel:',tel)

# Access the dictionary by calling the key
print("tel['jack']:", tel['jack'])
tel: {'jack': 4098, 'sape': 4139, 'guido': 4127}
tel['jack']: 4098
# Change the value of item
tel2 = {'jack': 4098, 'sape': [4139, 4029]}
print("tel2:", tel2)

# Overwrite the value
tel2['jack'] = 40 
print(tel2)
tel2: {'jack': 4098, 'sape': [4139, 4029]}
{'jack': 40, 'sape': [4139, 4029]}
# Try dic.update() function
d1 = {'jack': 4098, 'sape': 4139}
d2 = {'jack1': 4098, 'sape2': 4139}
d1.update( d2 )
d1
{'jack': 4098, 'sape': 4139, 'jack1': 4098, 'sape2': 4139}

Activity 5

Given:

  • numbers1 = [4, 5, 6]

  • numbers2 = (1, 2, 3)

  1. What’s the data types for each of them?

  2. Can you change the value with index=1 to equal to “10” for both of them? If yes, write the code to change the value, if not, explain why.

numbers1 = [4, 5, 6]
numbers2 = (1, 2, 3)
# write codes here

See solution for activity 5 at the end.

Solutions#

Activity 1#

# uncomment the code below to get help for the .replace function on Jupyter
#exa.replace?
exa = "this is an example"

print(exa.replace("is", "was"))
print(exa.replace("is", "was", 0))
print(exa.replace(" is", " was", 1))
thwas was an example
this is an example
this was an example

Activity 2#

s = 'GEM 530'
print(s[-2])
3

The output would be ‘3’ since it starts from the last character and the -1 is 0 and -2 is next to it.

print(s[3])
 

The output would be ‘ ‘ since it starts from the first character count it as zero position.

Activity 3#

a = 2
b = 4
a not in range(b)
False

Activity 4#

True and False or True
True

Explanation of the above code:
True and False is False. Then, False or True is True

(True or False) and False
False

Activity 5#

# 1. data types
numbers1 = [4, 5, 6]
numbers2 = (1, 2, 3)
print("Type of numbers1", type(numbers1))
print("Type of numbers2",type(numbers2))
Type of numbers1 <class 'list'>
Type of numbers2 <class 'tuple'>
# 2. We can change the value for list (numbers1 here), but not for tuple (numbers2).
numbers1[1] = 9 
print("numbers1", numbers1)

print(numbers2[1])
#numbers2[1] = 9 # uncomment this line to see what happen
numbers1 [4, 9, 6]
2