Chapter 1 Introduction

Python

Python is what is called an interpreted language.

Compiled languages examine your entire program at compile time, and are able to warn you about a whole class of errors prior to execution. In contrast, Python interprets your script line by line as it executes it. Python will stop executing the entire program when it encounters an error (unless the error is expected and handled by the programmer, a more advanced subject that we’ll cover later on in this course).

Jupyter Lab

to install on mac: pip3 install jupyterlab on terminal

to upgrade pip: pip3 install --upgrade pip on terminal

check python version

!python -V
# Check the Python Version

import sys
print(sys.version)
## 3.10.3 (v3.10.3:a342a49189, Mar 16 2022, 09:34:18) [Clang 13.0.0 (clang-1300.0.29.30)]

[Tip:] sys is a built-in module that contains many system-specific parameters and functions, including the Python version in use. Before using it, we must explicitly import it.

In Python, the concepts of objects and data types are closely related. In fact, in Python, everything is an object. Understanding this relationship is key to working effectively with Python.

1.1 Basics

1.1.1 Data Types in Python

  • Type of an Object: The type of an object in Python represents the kind of data that the object can hold. For example, the types int, float, str, etc., represent different categories of data.

  • Type Checking: You can use the type() function to determine the type of an object. For example:

x = 5
print(type(x))  # Output: <class 'int'>
  • Dynamic Typing: Python is dynamically typed, which means you don’t have to explicitly declare the type of a variable. The type is determined at runtime.

1.1.2 Objects vs Data Types

  • Every value in Python is an object, and every object has a type. For example, 5 is an object of type int.

  • Data types in Python are essentially classes, and objects are instances of those classes. When you create a variable, you are creating an instance of a specific data type (class).

  • You can think of data types as categories or blueprints for objects. They define the behavior and characteristics of objects.

1.1.3 Objects in Python

  • Everything is an Object: In Python, every value is an object, and every object has a type. This includes not only the fundamental data types (integers, floats, strings) but also more complex types like lists, dictionaries, and even functions.

  • Object Identity: Each object in Python has a unique identity, which can be obtained using the id() function. This identity is guaranteed to be unique and constant for the lifetime of the object.

  • Object Attributes and Methods: Objects in Python can have attributes (characteristics) and methods (functions associated with the object). For example, a string object has methods like upper() and attributes like length.

In Python, everything is an object. Objects are instances of classes, and Python is an object-oriented programming language. Here are some common types of objects in Python:

  1. Numbers:

    • int: Integer type, e.g., x = 5.
    • float: Floating-point type, e.g., y = 3.14.
    • complex: Complex number type, e.g., z = 2 + 3j.
  2. Strings:

    • str: String type, e.g., s = "Hello, World!".
  3. Collections:

    • list: Ordered collection of items, e.g., my_list = [1, 2, 3].
    • tuple: Immutable ordered collection of items, e.g., my_tuple = (1, 2, 3).
    • set: UNORDERED, MUTABLE collection of unique items, e.g., my_set = {1, 2, 3}.
    • dict: Key-value pairs, e.g., my_dict = {'a': 1, 'b': 2}.

List vs Tuple: - both ordered - tuple is IMMUTABLE

  1. Boolean:
    • bool: Boolean type, representing True or False.

x = True
y = False 

print(type(x))      # <class 'bool'>
  1. None:
    • None: A special object representing the absence of a value or a null value.
x = None

print(type(x))     # <class 'NoneType'>
  1. Functions:
    • Functions themselves are objects in Python. You can assign them to variables, pass them as arguments, and return them from other functions.
  2. Classes:
    • User-defined classes create objects. Instances of a class are objects.
  3. Modules:
    • Modules are also objects. When you import a module, you are working with an object.
  4. File Objects:
    • When you open a file, the file itself is an object in Python.
  5. Exceptions:
    • Exception instances are objects that represent exceptional conditions.
  6. Custom Objects:
    • Objects created from user-defined classes.

In Python, you can use the type() function to determine the type of an object, and the isinstance() function to check if an object is an instance of a particular class. The dynamic typing nature of Python allows objects to change types during runtime.

There are many different types of objects in Python. Let’s start with the most common object types: strings, integers and floats. Anytime you write words (text) in Python, you’re using character strings (strings for short). The most common numbers, on the other hand, are integers (e.g. -1, 0, 100) and floats, which represent real numbers (e.g. 3.14, -42.0).

1.2 Collections

  • list: Ordered collection of items, e.g., my_list = [1, 2, 3].

  • tuple: Immutable ordered collection of items, e.g., my_tuple = (1, 2, 3).

  • set: Unordered collection of unique items, e.g., my_set = {1, 2, 3}.

  • dict: Key-value pairs, e.g., my_dict = {'a': 1, 'b': 2}.

1.2.1 Properties

In Python, collections are built-in data types that can be used to group multiple elements together. Here are some common properties of Python collections:

1.2.1.1 Mutable vs. Immutable:

  • Mutable Collections: Lists (list), Sets (set), and Dictionaries (dict) are mutable. You can modify their contents after creation.

  • Immutable Collections: Tuples (tuple) and Strings (str) are immutable. Once created, their contents cannot be changed.

1.2.1.2 Ordering:

  • Ordered Collections: Lists and Tuples maintain the order of elements. Elements are stored in the order they were added.

  • Unordered Collections: Sets and Dictionaries do not guarantee any specific order. The order of elements may not be the same as the order of insertion.

1.2.1.3 Indexing and Slicing:

  • Indexing: Lists, Tuples, and Strings support indexing. Elements can be accessed using indices (0-based).
my_list = [1, 2, 3, 4]
print(my_list[0])  # Output: 1
  • Slicing: Lists, Tuples, and Strings support slicing to create sub-collections.
my_string = "Hello, World!"
print(my_string[0:5])  # Output: Hello

1.2.1.4 Uniqueness:

  • Unique Elements: Sets only contain unique elements. If you try to add an element that already exists, it won’t be added again.
my_set = {1, 2, 3, 1}
print(my_set)  # Output: {1, 2, 3}

1.2.1.5 Key-Value Pairs (Dictionaries):

  • Associative Data: Dictionaries consist of key-value pairs, allowing you to associate values with unique keys.
my_dict = {'name': 'John', 'age': 30, 'city': 'New York'}
print(my_dict['age'])  # Output: 30

1.2.1.6 Heterogeneity:

  • Mixed Types: Lists, Tuples, and Sets can contain elements of different data types.
my_list = [1, 'apple', 3.14]

1.2.1.7 Using append() for a Single Element:

list_1 = [1, 2, 3]

list_1.append(10)   # do not assign to a variable

print(list_1)
## [1, 2, 3, 10]

1.2.1.8 Using extend() for Multiple Elements:

list_2 = [1, 2, 3]

list_2.extend( [10, 100, 1000] )   # requires a list

print(list_2)
## [1, 2, 3, 10, 100, 1000]

1.2.1.9 Using add() for set objects:

set_3 = {1, 2, 3}

set_3.add( 100 )   # requires a list

print(set_3)
## {1, 2, 3, 100}
  • Removing Elements: remove(), pop(), discard(), clear()

1.2.1.10 remove() Method

Purpose: Removes the first occurrence of a specified value from a list or set.

Usage:

For lists, if the specified value does not exist, it raises a ValueError.

For sets, it also raises a KeyError if the specified value is not present.

my_list = [1, 2, 3, 2, 4]

# Removes the first occurrence of 2
my_list.remove(2)

print(my_list) 
## [1, 3, 2, 4]

1.2.1.11 pop() Method

Purpose: Removes and returns an element from a list or set.

Usage:

For lists, it removes and returns the element at a specified index. If no index is provided, it removes and returns the last element. Raises an IndexError if the list is empty.

For sets, it removes and returns an arbitrary element because sets are unordered. Raises a KeyError if the set is empty.

my_list = [1, 2, 3, 4]

# Removes and returns the last element by default
last_element = my_list.pop()

print(last_element)  # Output: 4
## 4
print(my_list)  # Output: [1, 2, 3]
## [1, 2, 3]
# Removes and returns the element at index 1
second_element = my_list.pop(1)

print(second_element)  # Output: 2
## 2
print(my_list)  # Output: [1, 3]
## [1, 3]

1.2.1.12 discard() Method

Purpose: Removes the specified element from a set.

Usage: Sets only: Does not raise an error if the specified element does not exist, unlike remove().

my_set = {1, 2, 3}

# Discards the element 2
my_set.discard(2)
print(my_set)  # Output: {1, 3}
## {1, 3}

1.2.1.13 clear() Method

Purpose: Removes all elements from a list or set, making it empty.

Usage: Can be used with both lists and sets to empty them.

my_list = [1, 2, 3]

# Clears all elements from the list
my_list.clear()

print(my_list)
## []

1.2.1.14 Extra: Imuutable

Yes, in Python, strings are immutable objects. This means that once a string is created, you cannot change its content. Any operation that appears to modify a string actually creates a new string. This immutability has several implications:

  1. No In-Place Modifications:
    • You cannot modify a string directly by changing a character at a specific index, like you can with a list.
    my_string = "Hello"
    # The following will result in an error
    my_string[0] = 'J'
  2. Creating New Strings:
    • Operations like concatenation or slicing create new strings rather than modifying the original.
    original_string = "Hello"
    new_string = original_string + ", World!"
  3. Hashing:
    • Because strings are immutable, they can be used as keys in dictionaries and elements in sets. Their hash value remains constant.
    my_set = {"apple", "banana", "cherry"}
  4. Memory Efficiency:
    • Python can optimize memory usage by reusing the same string in memory if it already exists, thanks to immutability.
    a = "Hello"
    b = "Hello"
    # Both a and b refer to the same string object in memory

Understanding the immutability of strings is important when working with them in Python to avoid unexpected behavior and to write efficient and correct code. If you need to modify a string, you typically create a new string with the desired changes.

1.2.2 1. lists

A list is a sequenced collection of different objects such as integers, strings, and even other lists as well. The address of each element within a list is called an index. An index is used to access and refer to items within a list.

Lists can contain strings, floats, and integers. We can nest other lists, and we can also nest tuples and other data structures. The same indexing conventions apply for nesting:

list are like tuples, ordered sequences.

But lists are mutable.

A list is a built-in data type used to store an ordered collection of items. Lists are mutable, which means you can modify their contents by adding, removing, or changing elements. Lists are defined using square brackets [].

Here’s an overview of lists and some example methods:

1.2.2.1 Creating Lists:


x = []    ## empty list
y = [1, 2, 3, 'apple', 'banana', 'cherry']
z = list( a_array)

1.2.2.2 Common List Methods:

  1. Accessing
# Accessing by index
my_list[0]  # Result: 1
## list index out of range
# Slicing
my_list[1:4]  # Result: [2, 3, 'apple', 'banana']
## []
  1. Append (append()):
    • Adds an element to the end of the list.
my_list.append("XXXX")

print(my_list)
## ['XXXX']
  1. Extend (extend()):
    • Extends the list by appending elements from another iterable.
another_list = [5, 6, 7]
   
my_list.extend(another_list)
   
print(my_list)
## ['XXXX', 5, 6, 7]
  1. Insert (insert()):
    • Inserts an element at a specified position.
    • this is not replacing !!!
my_list.insert(2, 'orange')

print(my_list)
## ['XXXX', 5, 'orange', 6, 7]
  1. Remove (remove()):
    • Removes the first occurrence of a specified value.
my_list.remove('banana')
## list.remove(x): x not in list
print(my_list)
## ['XXXX', 5, 'orange', 6, 7]
  1. Pop (pop()):

    • Removes and returns the element at the specified index. If no index is provided, it removes the last element.
popped_element = my_list.pop(2)

print(popped_element)
## orange
  1. Index (index()):
    • Returns the index of the first occurrence of a specified value.
index_of_apple = my_list.index('apple')
## 'apple' is not in list
print(index_of_apple)
## name 'index_of_apple' is not defined
  1. Count (count()):
    • Returns the number of occurrences of a specified value.
count_of_cherry = my_list.count('cherry')
print(count_of_cherry)
## 0
  1. Sort (sort()):

    • Sorts the list in ascending order. Optionally, you can specify reverse=True for descending order.
# Convert all elements to strings before sorting
st_list = [str(x) for x in my_list]

st_list.sort()

print(st_list)
## ['5', '6', '7', 'XXXX']
# Convert all elements to strings before sorting
sorted_list = sorted(map(str, my_list))

print(sorted_list)
## ['5', '6', '7', 'XXXX']
  1. Reverse (reverse()):

    • Reverses the order of the elements in the list.
my_list.reverse()

print(my_list)
## [7, 6, 5, 'XXXX']
  1. Concatenate lists
a_list = [1, 2, 3]
b_list = ["x", "y", "z"]


print(a_list + b_list)
## [1, 2, 3, 'x', 'y', 'z']

loop thru a list

a = ["a", "b", "c"]

for item in a:
   print(item.upper())
## A
## B
## C

1.2.3 tuples

Tuples are an ordered sequences of items, just like lists. The main difference between tuples and lists is that tuples cannot be changed (immutable) unlike lists which can (mutable).

tuples are collection of different type of objects.

Empty tuples

a = ()
b = tuple()

Create tuples

## 1. way
a_tuple = (1, 2.5, "string", [3, 4])

b = (1,)

print(b)
## (1,)

Concatenate tuples

b_tuple = ("a", "ab")

print(a_tuple + b_tuple)
## (1, 2.5, 'string', [3, 4], 'a', 'ab')

Immutable

element of a tuple can not be changed

a_tuple = (1,2,3,4,5)

a_tuple[5] = "a"

# TypeError: 'tuple' object does not support item assignment

sorted function

sorted() function: We can sort the tuple and assign a new name

sorted() function returns list type.

a_tuple = (1, 5, 2, 7, 4)

x = sorted(a_tuple)

print(x)
## [1, 2, 4, 5, 7]

Nesting

we can create nested tuples.

nested = (1, 2, ("a", "b", "c"), ("ayan", (4, 5)))

nested[2]
## ('a', 'b', 'c')

1.2.3.1 tuple methods

index method

The index method returns the first index at which a value occurs.

a = ("a", "b", "c", "d")

a.index("c")
## 2

count method

The count method returns the number of times a value occurs in a tuple.

a = ("a", "b", "c", "d", "a", "b", "c", "b")

a.count("b")
## 3

loop thru a tuple

a = ("a", "b", "a", "b")

for item in a:
   print(item)
## a
## b
## a
## b

1.2.4 Sets

Sets in Python are unordered collections of unique elements.

Unlike lists, sets do not have a specific order, and each element in a set must be unique. Here are some common operations and methods associated with sets:

  1. Creating Sets
my_set = {1, 2, 3, 4, 5}
  1. Adding Elements
my_set.add(6)  # Adds the element 6 to the set
  1. Removing Elements
my_set.remove(3)  # Removes the element 3 from the set
  1. Set Operations
set1 = {1, 2, 3, 4, 5}
set2 = {3, 4, 5, 6, 7}

# Union
union_set = set1.union(set2)  # Result: {1, 2, 3, 4, 5, 6, 7}

# Intersection
intersection_set = set1.intersection(set2)  # Result: {3, 4, 5}

# Difference
difference_set = set1.difference(set2)  # Result: {1, 2}

# Symmetric Difference
symmetric_difference_set = set1.symmetric_difference(set2)  # Result: {1, 2, 6, 7}

1.2.4.1 Set Methods

# Discard an element (if it exists)
my_set.discard(4)

# Clear all elements
my_set.clear()

# Copying a set
copy_of_set = my_set.copy()
  1. Other Operations
# Checking Membership
is_present = 5 in my_set  # Result: True

# Length of a Set
length_of_set = len(my_set)

Sets are useful when you need to work with unique elements, perform set operations, or check for membership efficiently. They do not support indexing or slicing, as they are unordered. Additionally, sets are mutable, meaning you can add and remove elements, but individual elements must be immutable (e.g., numbers, strings).

1.2.5 Dictionary

Dictionaries in Python are collections of key-value pairs, where each key must be unique. Dictionaries are versatile and used for mapping values to unique keys. Here are common operations and methods associated with dictionaries:

  1. Creating Dictionaries
my_dict = {'name': 'John', 
           'age': 30, 
           'city': 'New York'}

print(my_dict)
## {'name': 'John', 'age': 30, 'city': 'New York'}
  1. Accessing Values
# Accessing by key
p0 = my_dict['name']  # Result: 'John'

print(p0)
## John
# Using the get() method
p1 = my_dict.get('name')  # Result: 30

print(p1)
## John
  1. Modifying Dictionaries
# Updating a value
my_dict['age'] = 31

# Adding a new key-value pair
my_dict['gender'] = 'Male'
  1. Removing Items
# Removing a key-value pair
del my_dict['city']
my_dict = {'name': 'John', 
           'age': 30, 
           'gender': 'Male'}
           
# Using the pop() method
gender = my_dict.pop('gender')  # Removes the 'gender' key and returns its value

print(gender)
## Male

1.2.5.1 Dictionary Methods:

# Getting all keys
keys = my_dict.keys()  # Result: ['name', 'age']

print(keys)
## dict_keys(['name', 'age'])
# Getting all values
values = my_dict.values()  # Result: ['John', 31]

print(values)
## dict_values(['John', 30])
# Getting all key-value pairs as tuples
items = my_dict.items()  # Result: [('name', 'John'), ('age', 31)]

print(items)
## dict_items([('name', 'John'), ('age', 30)])

1.2.6 Iterating Over a Dictionary:

for key in my_dict:
    print(key, my_dict[key])
## name John
## age 30

1.2.7 Other Operations:

# Checking Membership (in terms of keys)
is_present = 'age' in my_dict  # Result: True

# Length of a Dictionary
length_of_dict = len(my_dict)

Dictionaries are widely used in Python for tasks that involve mapping keys to values, such as representing data structures, configuration settings, and more. They are mutable, meaning you can modify their contents by adding, updating, or removing key-value pairs. The keys must be immutable (e.g., strings, numbers), but the values can be of any type.

1.3 Data Types

In Python, data types are classifications that specify which type of value a variable can hold. Here are some of the basic and commonly used data types in Python:

  1. Numeric Types:
    • int: Integer type, e.g., x = 5.
    • float: Floating-point type, e.g., y = 3.14.
    • complex: Complex number type, e.g., z = 2 + 3j.
  2. Text Type:
    • str: String type, e.g., s = "Hello, World!".
  3. Sequence Types:
    • list: Ordered collection of items, e.g., my_list = [1, 2, 3].
    • tuple: Immutable ordered collection of items, e.g., my_tuple = (1, 2, 3).
  4. Set Types:
    • set: Unordered collection of unique items, e.g., my_set = {1, 2, 3}.
    • frozenset: Immutable version of a set.
  5. Mapping Type:
    • dict: Dictionary, a collection of key-value pairs, e.g., my_dict = {'a': 1, 'b': 2}.
  6. Boolean Type:
    • bool: Boolean, representing True or False.
  7. None Type:
    • NoneType (None): A special type representing the absence of a value or a null value.
  8. Binary Types:
    • bytes: Immutable sequence of bytes, e.g., b = b'hello'.
    • bytearray: Mutable sequence of bytes.
    • memoryview: A view object that exposes an array’s buffer interface.

These data types are the building blocks for creating variables, structures, and performing various operations in Python. You can use the type() function to check the type of a variable or value. For example:

x = 5
print(type(x))  # Output: <class 'int'>

y = 3.14
print(type(y))  # Output: <class 'float'>

s = "Hello, World!"
print(type(s))  # Output: <class 'str'>

Understanding and working with these data types is fundamental to writing Python code. Keep in mind that Python is dynamically typed, meaning you don’t need to explicitly declare the data type of a variable; it is determined at runtime.

1.3.1 Numeric Types

1.3.1.1 Mathematical Operations

Python supports a variety of mathematical operations, which can be performed on numerical data types. Here are some common mathematical operations in Python:

1.3.1.2 Arithmetic Operations:

  1. Addition (+):

    result = 5 + 3  # result is 8
  2. Subtraction (-):

    result = 5 - 3  # result is 2
  3. Multiplication (*):

    result = 5 * 3  # result is 15
  4. Division (/):

    result = 6 / 3  # result is 2.0 (float)
  5. Floor Division (//):

    result = 7 // 3  # result is 2 (integer division)
  6. Modulus (%):

    result = 7 % 3  # result is 1 (remainder after division)
  7. Exponentiation (**):

    result = 2 ** 3  # result is 8 (2 to the power of 3)

1.3.1.3 Comparison Operations:

  1. Equal to (==):
result = (5 == 3)  # result is False
  1. Not equal to (!=):

    result = (5 != 3)  # result is True
  2. Greater than (>): python result = (5 > 3) # result is True

  3. Less than (<): python result = (5 < 3) # result is False

  4. Greater than or equal to (>=): python result = (5 >= 3) # result is True

  5. Less than or equal to (<=): python result = (5 <= 3) # result is False

1.3.2 Other Mathematical Functions:

  1. Absolute Value (abs()): python result = abs(-5) # result is 5

  2. Round (round()): python result = round(3.14159, 2) # result is 3.14 (rounded to 2 decimal places)

  3. Minimum (min())/Maximum (max()): python minimum = min(1, 2, 3) # minimum is 1 maximum = max(1, 2, 3) # maximum is 3

These are just a few examples, and Python provides a rich set of mathematical functions and operations through the math module as well. To use it, you can import the math module and access functions like math.sqrt(), math.sin(), math.cos(), etc.

import math

# Square root
result = math.sqrt(16)  # result is 4.0

# Trigonometric functions (input in radians)
sin_result = math.sin(math.radians(30))  # result is 0.5 (sin of 30 degrees)
cos_result = math.cos(math.radians(60))  # result is 0.5 (cos of 60 degrees)
tan_result = math.tan(math.radians(45))  # result is 1.0 (tan of 45 degrees)

# Logarithmic functions
log_result = math.log(100, 10)  # result is 2.0 (log base 10 of 100)

# Exponential function
exp_result = math.exp(2)  # result is approximately 7.389

# Constants
pi_value = math.pi  # value of pi (3.141592653589793)
euler_number = math.e  # Euler's number (2.718281828459045)

# Other functions
factorial_result = math.factorial(5)  # result is 120 (5!)

1.3.3 Converting data types

numeric to string

a = 5.5

b = str(a)

string to numeric

float("1.1")
## 1.1

it does not convert directly to integer here

int("1.123")

but it works when transforming to float then integer.

int(float("10.123"))

boolean to numeric

True becomes 1

bl = True

int(bl)
## 1

numeric to boolean

0 becomes False

all other numbers are True

bool(-100)
## True
bool(0)
## False

1.3.4 Strings

A string is a sequence of characters, and it is one of the basic data types used to represent text.

Strings are enclosed in either single quotes (') or double quotes ("), and you can use either as long as the opening and closing quotes match. Here are some key characteristics and operations related to strings in Python:

  1. Creating Strings
# Using single quotes
single_quoted_string = 'Hello, Python!'

# Using double quotes
double_quoted_string = "Hello, Python!"

# Triple-quoted strings for multiline strings
multiline_string = '''This is a
multiline string.'''
  1. String Concatenation
first_name = "John"
last_name = "Doe"
full_name = first_name + " " + last_name

print(full_name)
## John Doe
  1. String Indexing and Slicing
my_string = "Python"

# Indexing (0-based)
first_char = my_string[0]  # Result: 'P'

print(first_char)
## P

syntax: string_object[from : to : increment]

# Slicing
substring = my_string[1:4:1]  # Result: 'yth'
substring = my_string[1:4]  # Result: 'yth'

print(substring)
## yth
print(my_string[::2])
## Pto

1.3.4.1 String Methods:

# Length of a string
length = len(my_string)  # Result: 6

# Converting to uppercase and lowercase
uppercase_string = my_string.upper()
lowercase_string = my_string.lower()

# Finding a substring
index_of_th = my_string.find("th")  # Result: 2

# Replacing a substring
new_string = my_string.replace("on", "er")  # Result: 'Pyther'

upper() : strings to uppercase lower() : strings to lowercase capitalize() : strings to uppercase 1st letter

"Ohh! my life!".upper()
## 'OHH! MY LIFE!'
"OHH! MY LIFE!".lower()
## 'ohh! my life!'
"star".capitalize()
## 'Star'
"is it lowercase".islower()
## True
"IS IT UPPERCASE".isupper()
## True

replace method

replace(): to replace part of a string syntax: string.replace(oldvalue, newvalue, count)

query = '''
select all_columns 
from my_table
'''
new_query = query.replace("my_table", "new_table_name")

print(new_query)
## 
## select all_columns 
## from new_table_name

strip method

strip() method removes leading or trailing white-space

raw_text = "   my text   "

raw_text.strip()
## 'my text'

split method

split() method breaks a string by specified character.

split() method returns list type.

raw_text = "banana, apple, cherry"

alist = raw_text.split(",")

alist
## ['banana', ' apple', ' cherry']

there are some whitespce in the splitted elements in the list

alist[1].strip()
## 'apple'

1.3.4.2 String Formatting:

name = "Alice"
age = 25
formatted_string = f"My name is {name} and I am {age} years old."
# Result: 'My name is Alice and I am 25 years old.'

1.3.4.3 Escape Characters:

escaped_string = "This is a line.\nThis is a new line.\tThis is a tab."

print(escaped_string)
## This is a line.
## This is a new line.  This is a tab.

1.3.4.4 Raw Strings:

raw_string = r"This is a raw string \n\t No escape characters here."

print(raw_string)
## This is a raw string \n\t No escape characters here.

1.3.4.5 Membership and Operations:

# Checking membership
contains_py = 'py' in my_string  # Result: True

# String repetition
repeated_string = my_string * 3  # Result: 'PythonPythonPython'

Strings in Python are immutable, meaning once a string is created, you cannot modify its contents. Any operation that appears to modify a string actually creates a new string. Understanding these operations and methods is crucial for working effectively with strings in Python.

1.3.4.6 loop thru strings

my_txt = "life"

for i in my_txt:
  print(i.upper())
## L
## I
## F
## E

1.3.4.7 in/not in

check if a phrase is present in a string

my_txt = "life is good!"

print("good" in my_txt)
## True
if "good" in my_txt:
  print(my_txt)
## life is good!
if "bad" not in my_txt:
  print("'bad' is not in the text")
## 'bad' is not in the text

1.4 Control Statements

1.4.1 Looping

1.4.2 Looping Extra

  1. for-else
for i in range(5):
    break
else:
    print('else runs cus break didnt run')

In a for-else block, if break runs in the for loop, else doesn’t run if break doesn’t run in the for loop, else will run

  1. break vs continue

break stops the entire loop immediately when L=‘C’, break happens, and the entire loop stops.

LETTERS = ['A', 'B', 'C', 'D', 'E']

for L in LETTERS:
  if L == 'C':
    break
  print(L)
## A
## B

continue skips to the next iteration, without stopping the entire loop

LETTERS = ['A', 'B', 'C', 'D', 'E']

for L in LETTERS:
  if L == 'C':
    continue
  print(L)
## A
## B
## D
## E

when L='C', continue executes, and the iteration for L='C' is skipped.

Instead we skip over to the next iteration immediately, where L='D'

Note that continue doesn’t stop the entire loop like break

  1. enumerate & zip

enumerate() allows us to generate both index and element at the same time when iterating through a list (or similar iterable).

LETTERS = ['A', 'B', 'C', 'D', 'E']

for i,L in enumerate(LETTERS):
    print(i, L)
## 0 A
## 1 B
## 2 C
## 3 D
## 4 E

zip() allows us to iterate through 2 or more lists (or iterables) at once.

names = ['Adam', 'Eve', 'Joe']
ages = [25,35,18]

for name, age in zip(names, ages):
    print(name, age)
## Adam 25
## Eve 35
## Joe 18

1.5 Functions

In Python, return and print are used for different purposes in functions:

  1. return:

    • return is used to send a value back to the caller of the function.
    • When a function is called, it can perform some operations and then use return to send a result back to where the function was called.
    • The value returned by return can be stored in a variable, used in expressions, or passed to other functions.
    • Once return is executed, the function terminates immediately.

    Example:

    def get_greeting():
        return "Hello, World!"
    
    greeting = get_greeting()
    print(greeting)  # This will print: Hello, World!
  2. print:

    • print is used to output text to the console.
    • It does not send any value back to the caller; it simply outputs the given string or other data types to the standard output (usually the console).
    • The function continues executing after print unless it encounters a return or another statement that terminates the function.

    Example:

    def display_greeting():
        print("Hello, World!")
    
    display_greeting()  # This will print: Hello, World!

1.5.1 Summary

  • Use return to send a value from the function to its caller.
  • Use print to display a value to the console.

Here is a combined example to illustrate both:

def get_and_display_greeting():
    greeting = "Hello, World!"
    print(greeting)  # This will print: Hello, World!
    return greeting

returned_value = get_and_display_greeting()
print(f"The returned value is: {returned_value}")  # This will print: The returned value is: Hello, World!

In this example, print outputs the greeting to the console, and return sends the greeting back to where the function was called, which is then stored in returned_value and printed again.

Note:Functions that do not have an explicit return expression will implicitly return the None object. The details of None will be covered in a later exercise. For the purposes of this exercise and explanation, None is a placeholder that represents nothing, or null: