"Lists are the backbone of data organization."
- Mutable and Immutable data types
- Introduction to
list
- Functions (
len()
,sum()
,min()
,max()
,sorted()
) - Methods of Lists
- General methods of Iterations
- Copying lists
join()
andsplit()
- Quiz
- Homework
Mutable data types - are those that can be changed after they are created.
This means you can change
, add
, or remove
elements from these data types without creating a new object.
In programming, understanding the difference between mutable
and immutable
data types is extremely crucial because it affects how you work with data and how your program behaves.
This lesson we will be focusing on the list
. But during next lessons we will get acquainted with tuple
, set
and dict
data structures. So it will be the best to know which of them are mutable/immutable
now.
Data Structure | Description | Example Operations |
---|---|---|
List |
Ordered and changeable collection of items | append() , remove() , pop() |
Dictionary |
Unordered collection of key-value pairs | update() , clear() , pop() |
Set |
Unordered collection of unique items | add() , discard() , pop() |
Immutable data types cannot be changed after their creation. Any "modification" creates a new object instead of changing the old one.
Data Type | Description | Example Operations |
---|---|---|
Tuple |
Ordered and unchangeable collection | Accessing items, index() , count() |
String |
Sequence of characters | Accessing characters, concatenation creates new string |
Integer |
Whole number without decimal | Arithmetic operations create new integers |
Float |
Number with decimal point | Arithmetic operations create new floats |
Boolean |
Represents True or False |
Used in logical operations |
We have already seen the error trying to modify strings in the Lesson #3. Instead of this we created a new variable to store a new object.
greeting = "Hello, world!"
print(f"Original string: {greeting}")
# Attempting to change a character
greeting[0] = 'J' # This line will actually raise an error: TypeError
# Concatenation creates a new string and
greeting = 'J' + greeting[1:]
print(f"Modified string: {greeting}")
Original string: Hello, world!
Traceback (most recent call last):
File "<string>", line 5, in <module>
TypeError: 'str' object does not support item assignment
But how to verify if we created a new object? This can be done using id()
function.
The id()
function in Python
returns a unique identifier for an object, which corresponds to its location in memory. Actually, almost any operation that seems to modify the object actually creates a new object, if you work with immutable objest.
If the id()
is different, it confirms that a new object was created. This can be helpful while debugging the project.
# Immutable data type
greeting = "Hello, world!"
original_id = id(greeting)
greeting = 'J' + greeting[1:]
new_id = id(greeting)
# Comparing identifiers
print(f"Original ID: {original_id}")
print(f"Modified ID: {new_id}")
Original ID: 140353776296400
Modified ID: 140353776295680
In Python
a list
- is a mutable, ordered sequence of items.
It is one of the most versatile data types and can contain items of different data types, though typically, lists contain items of the same type.
In order to create an empty list
we can use the following syntax:
list_1 = []
list_2 = list()
print(list_1, list_2)
print(type(list_1), type(list_2))
[] []
<class 'list'> <class 'list'>
Most iterable types
in Python
can be converted to a list using the list constructor.
Simply speaking, iterable types
- objects that can give you elements one by one, which allows you to iterate through them using loops or advanced techniques such as iterators/generators.
string_to_list = list('hello')
print(string_to_list)
['h', 'e', 'l', 'l', 'o']
Indexing
and slicingwork
the same way forlists
as they do withstrings
.
Indexing
-> access individual items.
slicing
-> access a range of items.
my_list = [10, 20, 30, 40, 50]
# Indexing
print(my_list[1]) # 20
# Slicing
print(my_list[1:4]) # [20, 30, 40]
Lists
can be concatenated using the +
operator, which combines them into a new list.
list_one = [1, 2, 3]
list_two = [4, 5, 6]
list_three = [7, 8, 9]
# Concat
new_list = list_one + list_two + list_three
print(new_list)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
In Python
, lists can be multiplied by an integer, which repeats the list's elements.
# Example 1
numbers = [1, 2, 3]
new_numbers = numbers * 3
print(new_numbers)
# Example 2
names = ["Sasha", "Yehor"]
triple_names = names * 3
print(triple_names)
[1, 2, 3, 1, 2, 3, 1, 2, 3]
['Sasha', 'Yehor', 'Sasha', 'Yehor', 'Sasha', 'Yehor']
In some cases this can be very useful and it's very cool that Python
provides such functionality.
The +=
and *=
operators can modify lists in place. These operators perform the same operations as list + list
and list * int
. respectively.
# +=
nums = [1, 2, 3]
nums += [4, 5] # Same as ``nums = nums + [4,5]``
print(nums)
# *=
nums = [1, 2, 3]
nums *= 2 # Same as ``nums = nums * 2``
print(nums)
[1, 2, 3, 4, 5]
[1, 2, 3, 1, 2, 3]
Unpacking is the way of extracting values from iterable objects.
There are several types of unpacking presented in Python
.
The number of variables on the left side of the assignment operator should match the number of elements in the iterable.
my_list = [1, 2, 3]
a, b, c = my_list # Unpacking list into three variables
print(a) # 1
print(b) # 2
print(c) # 3
Using a star *
expression, a.k.a (asterix
), allows you to assign a portion of an iterable to a variable and the rest to another variable.
my_list = [1, 2, 3, 4, 5]
first, *middle, last = my_list
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
This lifehack can be used to swap the values of two variables efficiently without needing a temporary variable.
x, y = 10, 20
x, y = y, x # Swapping values
print(x) # 20
print(y) # 10
As it was mentioned before, list
can consist of different types of elements and it being a list
of lists. That's where nested loops come in handy. Alternatively, you can use that to upack variables with the same format.
nested_lists = [[1, 'apple'], [2, 'banana'], [3, 'cherry']]
for number, fruit in nested_lists:
print(f"Number: {number}, Fruit: {fruit}")
Number: 1, Fruit: apple
Number: 2, Fruit: banana
Number: 3, Fruit: cherry
The in operator in Python is used to check if a value exists within all iterable objects
.
my_list = [1, 2, 3, 4, 5, 'banana']
print(3 in my_list) # True
print(6 in my_list) # False
print("banana" in my_list) # True
print("a" in my_list[-1]) # True
Python provides several built-in
functions that are readily available for common list operations. These functions can quickly perform actions on list items without the need for loops
.
They work with all iterables beause they use polymorphism
concept which we will explore in OOP (Object Oriented Programming).
The len()
function returns the number of items in a list (the length of the list).
numbers = [1, 2, 3, 4, 5]
print(len(numbers))
matrix = [[1,2,3], [4,5,6]] # we have 2 lists in the list, heh
print(len(matrix))
5
2
The sum()
function calculates the total sum of all numerical items in a list.
numbers = [1, 2, 3, 4, 5]
print(sum(numbers))
# This won't work, as we need only ``numberical data types``
imposter_numbers = ["STRING???", 15]
print(sum(imposter_numbers))
15
Traceback (most recent call last):
File "<string>", line 5, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
The min()
function returns the smallest item in a list.
numbers = [5, 1, 8, 3, 2]
print(min(numbers))
# This won't work, as well
imposter_numbers = [[1, 2, 3], 15]
print(min(imposter_numbers))
1
Traceback (most recent call last):
File "<string>", line 2, in <module>
TypeError: '<' not supported between instances of 'list' and 'int'
The max()
function returns the biggest item in a list.
numbers = [5, 1, 8, 3, 2]
print(max(numbers)) # Output: 8
# Nope, won't work, don't even try to do this!
imposter_numbers = ['s', 15]
print(min(imposter_numbers))
8
Traceback (most recent call last):
File "<string>", line 2, in <module>
TypeError: '<' not supported between instances of 'list' and 'int'
Suppose we have the following list
:
x = [1, 2, 3]
These methods let you add elements to the list, either at the end, a specific position, or extending the list by appending all elements from another iterable
(str
, list
, tuple
, dict
, etc..) / numbers.
Method | Description | Example | Output |
---|---|---|---|
append(x) |
Adds an item to the end of the list. | x.append(4) |
[1, 2, 3, 4] |
extend(iterable) |
Extends the list by appending elements from the iterable . |
x.extend([5, 6]) |
[1, 2, 3, 4, 5, 6] |
insert(i, x) |
Inserts an item at a given position. | x.insert(2, 'a') |
[1, 2, 'a', 3, 4] |
Method | Description | Example | Output |
---|---|---|---|
remove(x) |
Removes the first item from the list whose value is x. | x.remove('a') |
[1, 2, 3, 4] |
pop([i]) |
Removes the item at the given position in the list, and returns it. | x.pop(1) |
[1, 3, 4] |
clear() |
Removes all items from the list. | x.clear() |
[] |
Method | Description | Example | Output |
---|---|---|---|
index(x) |
Returns the index of the first item whose value is x. | x.index(3) |
2 |
count(x) |
Returns the number of times x appears in the list. | x.count(1) |
1 |
sort() |
Sorts the items of the list in place. | x.sort() |
[1, 2, 3, 4] |
reverse() |
Reverses the elements of the list in place. | x.reverse() |
[4, 3, 2, 1] |
copy() |
Returns a shallow copy of the list. | new_x = x.copy() |
new_x is [1, 2, 3, 4] |
Don't hesitate to use dir()
, and help()
functions or refer to the official Python
documentation working with list.
Iterating through lists is a fundamental aspect of working with data in Python
, as it allows you to access and manipulate each item in a list.
As with strings, Python
provides several ways to iterate over lists.
The for in
loop is the most common way to iterate through each item in a list directly.
fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
print(fruit)
apple
banana
cherry
cities = ['London', 'Kyiv', 'Washington']
for city in cities:
print(city)
London
Kyiv
Washington
Don't forget to call variables which make sense for data scructures like in the examples above.
Sometimes, you need the index of the items while iterating through a list. In this case, you can use range()
along with len()
to iterate through the list indexes.
fruits = ['apple', 'banana', 'cherry']
for index in range(len(fruits)):
print(f"Index: {index}, Fruit: {fruits[index]}")
Index: 0, Fruit: apple
Index: 1, Fruit: banana
Index: 2, Fruit: cherry
This method is useful when you need to modify the list items since you can use the index to set new values.
Ccpying lists can be tricky, especially for beginners, because of the way Python handles object references.
There are two primary ways how we can copy
list objects:
Shallow copying creates a new list object, but it doesn't create new objects for the elements inside the list. It only copies references to those elements.
Be carefull using this copying.
original_list = [1, 2, 3]
copied_list = original_list.copy()
print("Original List:", id(original_list))
print("Copied List:", id(copied_list))
In this case, original_list
and copied_list
have different ids, meaning they are different objects.
Original List: 139827368587200
Copied List: 139827364976256
However, the elements inside them are still referencing the same objects.
print(id(original_list[1]))
print(id(copied_list[1]))
139827364976123
139827364976123
Assigning a reference to a list in Python
is an operation that can lead to unintended consequences. It's often confused with creating a copy of the list, but in reality, it's quite different.
What Happens When You Assign a Reference?
original_list = [1, 2, 3]
reference_list = original_list # assign a list to one and another variable
# Output the ids
print("Original_list_id: ", id(original_list))
print("Reference_list_id: ", id(reference_list))
# Made a change to both ``original_list`` and ``reference_list``
original_list += [4]
reference_list += [5]
# Output lists
print("Original List:", original_list)
print("Reference List:", reference_list)
Any changes made to original_list
will also appear in reference_list
, and vice versa. This is because they are two names for the same object.
If your intention was to work with two separate lists, this behavior could lead to bugs. Changes you thought were made to only one list can unexpectedly affect the other.
Original_list_id: 140583473131392
Reference_list_id: 140583473131392
Original List: [1, 2, 3, 4, 5]
Reference List: [1, 2, 3, 4, 5]
Imagine, you have lists containing other mutable objects (e.g list of lists), it's crucial to understand the concept of deep copying
, when working with more complex data structures.
A deep copy
creates a new list with entirely new objects, even for nested mutable objects.
Creating a New List
: A completely new list object is created.Copying Nested Objects
: Even the nested objects within the list are copied and recreated in the new list.Independent Objects
: Changes made to the deep-copied list (or its nested objects) do not affect the original list, and vice versa.
"""Python provides a module named `copy`, which has a method `deepcopy()` for this purpose."""
import copy
# Original list with nested mutable objects
original_list = [[1, 2, 3], [4, 5, 6]]
# Creating a deep copy
deep_copied_list = copy.deepcopy(original_list)
# Modifying the deep copied list
deep_copied_list[0][1] = 'Changed'
print("Original List:", original_list)
print("Deep Copied List:", deep_copied_list)
Original List: [[1, 2, 3], [4, 5, 6]]
Deep Copied List: [[1, 'Changed', 3], [4, 5, 6]]
Deep copying is more memory-intensive and can be slower for large lists. So use it wisely. For example, when your list contains nested mutable objects
, and you need complete independence between the original and copied data
.
List comprehensions provide a more readable and concise way to create lists by transforming each element of an iterable.
They can be used in place of for loops for simplicity and efficiency, particularly when you're applying a single, straightforward transformation or condition to each element. Btw, they work faster than default loops.
# Default option
[expression for item in iterable]
# If clause
[expression for item in iterable if condition]
# If-else clause
[if expression else expression for item in itreable]
Basically it is the same as for loops and each list comprehension
can be rewriten into the for
loop.
# List comprehension
squares = [i ** 2 for i in range(5)]
# For loop equivalant
squares = []
for i in range(5):
squares.append(i ** 2)
[0, 1, 4, 9, 16]
# List comprehension
even_squares = [i ** 2 for i in range(10) if i % 2 == 0]
# For loop
even_squares = []
for i in range(10):
if i % 2 == 0:
even_squares.append(i ** 2)
[0, 4, 16, 36, 64]
# List comprehension
even_or_odd = ["even" if i % 2 == 0 else "odd" for i in range(5)]
# For loop
even_or_odd = []
for i in range(5):
if i % 2 == 0:
even_or_odd.append("even")
else:
even_or_odd.append("odd")
["even", "odd", "even", "odd", "even"]
But there is some known rules between programers where we should or shouldn't use them.
In some cases for simple operation it is a great tool which working much faster in terms of efficiency, but in other cases it can lead to unnecessary complexion which will result in bad maintainability and readability of the code.
Please, be careful using list comprehesnsions.
Loops |
List Comprehension |
|
---|---|---|
Advantages | - More flexible - Easier to read with complex logic - Allows complex operations |
- More concise and readable for simple cases - Can be written as a single line - Often faster for creating a list |
Disadvantages | - Can be verbose for simple operations | - Can be less readable with complex expressions - Not suitable for multi-step operations |
When to Use | - When you have complex logic - When the loop involves nested loops, conditions, or other complex operations |
- When you're applying a single operation to all items - When you need a quick and readable one-liner to create/update a list |
Note: Always prioritize readability and maintainability of your code when choosing between a for-loop
and a list comprehension
.
In Python, join()
and split()
are two powerful methods that bridge the gap between strings
and lists
. They are widely used in data manipulation, especially in tasks involving text processing and data formatting.
The join()
method in Python
is a string
method that takes all items in an iterable and joins them into one string.
words = ['Python', 'is', 'extremly', 'powerful', 'and', 'complicated' 'language']
s = ' '.join(words)
print(s)
Python is extremly powerful and complicated language
# I'm kidding Assembler is harder :)
A string can be specified as the separator, by default it is a whitespace
as you have seen from the example above.
separator = ', '
my_list = ['apple', 'banana', 'cherry']
joined_string = separator.join(my_list)
print(joined_string)
apple, banana, cherry
The split()
method splits a string into a list where each word is a list item.
The splitting is done at the specified separator. If no separator is defined, whitespace
is used by default.
text = 'apple, banana, cherry'
split_list = text.split(', ')
print(split_list)
['apple', 'banana', 'cherry']
text = 'one two three four'
split_text = text.split(' ', 2) # You can the max number of splits as well.
print(split_list)
['one', 'two', 'three four']
These methods are especially powerful when used together, allowing for back-and-forth transformations between strings
and lists
.
Sure, here's the corrected and reformatted list for your quiz:
What will be the output of the following code?
my_list = [10, 20, 30, 40, 50]
print(my_list[-2])
What will be the output of the following code snippet?
my_list = [1, 2, 3, 4, 5]
my_list.append(6)
print(my_list)
Which of the following is a correct way to create a deep copy of a list?
original_list = [[1, 2], [3, 4]]
A)
new_list = original_list
B)
new_list = original_list.copy()
C)
new_list = list(original_list)
D)
import copy
new_list = copy.deepcopy(original_list)
What does the
extend()
method do to alist
?
A) Extends the list by adding a single element at the end.
B) Increases the size of the list without adding elements.
C) Adds multiple elements at the specified index of the list.
D) Appends all elements of an iterable to the end of the list.
What is the output of the following list comprehension?
numbers = [x for x in range(10) if x % 2 == 1]
A) [0, 2, 4, 6, 8]
B) [1, 3, 5, 7, 9]
C) [2, 4, 6, 8, 10]
D) [1, 2, 3, 4, 5]
What is the result of the following operation?
my_list = [1, 2, 3]
my_list.insert(1, 'a')
print(my_list)
A) [1, 'a', 2, 3]
B) ['a', 1, 2, 3]
C) [1, 2, 'a', 3]
D) An error occurs
What is the purpose of the
reverse()
method in alist
?
A) To sort the list
in ascending order.
B) To randomly shuffle the elements of the list
.
C) To reverse the order of the elements in the list
.
D) To remove the last element of the list
.
Which of the following is a valid example of slicing a list?
A) my_list[1:]
B) my_list[:1]
C) my_list[1:3]
D) All of the above
Which of the following statements about list comprehensions is NOT true?
A) They are more memory-efficient than for-loops.
B) They can include conditional logic.
C) They can make code less readable if overused.
D) They cannot be used to create lists from other iterables.
How does pop() differ from remove() when used on a list?
A) pop()
deletes an item by value, whereas remove()
deletes by index.
B) pop()
returns the removed item, whereas remove()
does not.
C) remove()
can delete multiple items, whereas pop()
can only delete one.
D) There is no difference in their functionality.
Objective: Create a program that allows the user to manipulate a list in various ways. The user can choose to add
, remove
, or modify
items in the list
.
# Example of how the program should work
# Menu:
# 1. Add an item
# 2. Remove an item
# 3. Modify an item
# User chooses option 1:
Enter the item to add: Apple
Output: List: ['Apple']
# User chooses option 2:
Enter the item to remove: Apple
Output: List: []
# User chooses option 3:
Enter the item to modify: Apple
Enter the new item: Banana
Output: List: ['Banana']
Objective: Develop a program that allows users to input
a list
and then perform various slicing operations
based on the user input
.
Input:
Enter a list of numbers (separated by space): 10 20 30 40 50
Enter start index for slicing: 1
Enter stop index for slicing: 4
Output:
The sliced list is: [20, 30, 40]
Objective: Develop a program that helps users organize their shopping list by adding
, viewing
, removing
and sort
items alphabetically.
Additionaly, we would want to check if we add strings to the list. You can use isinstance()
function documented here.
# Example of how the program should work
# Menu:
# 1. Add item
# 2. View list
# 3. Remove item
# 4. Exit
# User Interaction Example:
Choose an action (add/view/remove/exit): add
Enter an item to add: Milk
Item added. Your current list is: ['Milk']
Choose an action (add/view/remove/exit): add
Enter an item to add: Eggs
Item added. Your current list is: ['Eggs', 'Milk']
Choose an action (add/view/remove/exit): add
Enter an item to add: 25
Sorry, it is not a string
Choose an action (add/view/remove/exit): view
Shopping List: ['Eggs', 'Milk']
Choose an action (add/view/remove/exit): remove
Enter an item to remove: Milk
Item removed. Your current list is: ['Apples']
Choose an action (add/view/remove/exit): exit
Exiting the program.
# Don't forget to check if item exists in the list using operator ``in``
Objective: Generate a list of even elements using range from the input and display the average of them.
# Write a list comprehension here:
Objective: Enhance the provided code to perform the following actions on a given list
:
- Display the length of the
list
. - Print the
sum
,max
,min
and the average. - Print the last element of the
list
. - Output the
list
inreverse
order (remember to useslicing
). - Print
"YES"
if the list contains the numbers4
and9
, and"NO"
otherwise. - Show the list with the
first
andlast
elements removed (create a new list object instead). - Print the third element from the end of the list.
- If the second element
is > 10
, replace it with 10.
# Sample List
numbers = [3, 12, 5, 8, 4, 9, 15, 22]
# Your code here
Objective: Write a program which creates alphabet and stores it into the list
.
# Output
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
Objective: Develop a program that stores detailed information about a person using nested lists.
Input:
Enter your name: Alice
Enter your age: 30
Enter your hobbies (separated by commas): reading, gardening, gaming
Enter your favorite foods (separated by commas): pizza, salad, ice cream
Output:
Personal Information:
Name: Alice
Age: 30
Hobbies: ['reading', 'gardening', 'gaming']
Favorite Foods: ['pizza', 'salad', 'ice cream']
# Later on, the program can provide options to view, update and remove these details.
Objective: Write a program that validates if the entered string is a correct IP address.
Note: An IP address is considered valid if it consists of four non-negative integers separated by dots, with each integer ranging from 0 to 254 inclusive.
## Example
Input: 192.168.1.1
Output: Correct
Input: 256.300.2.10
Output: Incorrect
Objective: Create a program to extract email addresses from a block of text. The program should identify strings that resemble email addresses and separate them using semicolons.
# Input:
Enter the text: Please contact us at support@example.com or sales@example.com for assistance.
# Output:
Extracted Emails: support@example.com; sales@example.com
Don't forget that while designing your program, remember to create an interactive and user-friendly interface!