A Complete Guide to Python Lists. Use Python Lists Like a Pro | by Erdem Isbilen | Towards Data Science

In this article, I will try to explain Python lists, along with exploring why and when to use them, meanwhile giving you some hints about the correct usage of the list methods.
Let’s understand the Python list data structure in detail with step by step explanations and examples.
Lists are one of the most frequently used built-in data structures in Python. You can create a list by placing all the items inside square brackets[ ], separated by commas. Lists can contain any type of object and this makes them very useful and versatile.
Fundamental characteristics of Python lists are as follows; They are mutable, ordered, dynamic and array type (sequence) data structures.
Let's explore each of these characteristics in further detail;
New elements can be added or removed in runtime. Lists are not fixed-size objects, so they are called dynamic data structures.
The order in which you specify the elements when you define a list is maintained so lists are ordered.
Lists can be changed after they have created. You can add, remove items or modify the value of available items in a list object. You do not need to have another copy of the list object for revising it. So they are called mutable objects.
Although Python lists are not fixed-size and constrained like arrays in C++ or Java, they are still array type data structures where the items contained are stored in memory sequentially and accessed by an index number representing the memory block for a specific element.
A list object can contain duplicate elements.
Let’s get a basic understanding of when to use lists over another data structure;
When we want to store a collection of heterogeneous objects in a single data structure. We can store any of the primitive data structures (integers, strings, etc) as well as compound data structures (lists, sets, tuples, dictionaries, etc) inside a single list object.
When we want to keep the order of data unchanged. The order we put the items into the lists is preserved, we can access the elements with the same order as we put them in.
As lists are mutable, it is not a good idea to use them to store data that shouldn’t be modified in the first place.
There are many ways of creating and initializing lists. You can use square brackets[] or list(iterable) constructor as shown below;
# empty list with square bracketsempty_list = []# empty list with square bracketsempty_list = list()# list of integersinteger_list = [1, 2, 3]# list with mixed data typesmixed_list = [8, "Hi", 3.3]# list from an iterable objectiterable_list = list("data-science")# a nested listnested_list = ["cat", [3, 2], ['d']]
Lists support sequence operations like Indexing, Slicing, Membership, Concatenation, Length, Iteration and some others as they are sequence type objects.
Indexing: Items in a list can be accessed by index using the indexing operator. Python indexing starts from zero. You can use a negative integer as an index to start from the end. If you use an integer beyond the length of the list then you get IndexError. If your index is not an integer then you get TypeError.
# list of integersinteger_list = [1, 2, 3, 4, 5, 6, 7]# indexingprint(integer_list[0]) #prints '1'print(integer_list[6]) #prints '7'print(integer_list[-1]) #prints '7'print(integer_list[-7]) #prints '1'print(integer_list['a']) #TypeError: list indices must be integersprint(integer_list[7]) #IndexError: index is out of range
Slicing: Slicing is a very powerful tool as it helps you to create another list with a range of objects contained in the original list. Slicing seems unintuitive at first but once you grasp the basics you can use them easily to enhance the lists operations.
# list of integersinteger_list = [1, 2, 3, 4, 5, 6, 7]# slicing with [start:end] takes all elements between 'start' to 'end' does not include 'end' elementprint(integer_list[1:3]) # prints '[2, 3]'# slicing with [start:] includes all elements after 'start' including the 'start'th  elementprint(integer_list[2:]) # prints [3, 4, 5, 6, 7]# slicing with [:ends] includes all elements before 'end' and does not include the 'end'th elementprint(integer_list[:5]) # prints [1, 2, 3, 4, 5]# slicing with negative integers to start indexing from endprint(integer_list[-2:]) # prints [6, 7]print(integer_list[:-5]) # prints [1, 2]
Membership: You can check whether if a specific object is member of a list or not. To do this, you can use ‘in’ or ‘not in’ membership operators.
# list of integersinteger_list = [1, 2, 3, 4, 5, 6, 7]# membership4 in integer_list # True8 in integer_list # False4 not in integer_list # False8 not in integer_list # True
Concatenation: You can use ‘+’ or ‘*’ operators to concatenate the lists.
# list of integersinteger_list = [1, 2, 3]# list of stringsstring_list = ['data', 'science', 'rocks']integer_list + string_list #[1, 2, 3, 'data', 'science', 'rocks']string_list * 2 #['data', 'science', 'rocks', 'data', 'science', 'rocks']
Length: If you pass a list to the ‘len()’ function, you will get the length of the list in return.
# list of integersinteger_list = [1, 2, 3, 4, 5, 6, 7]# len() with listslen(integer_list) # 7
Iteration: Lists are iterable objects so you can iterate over a list with for loops.
# list of integersinteger_list = [1, 2, 3]# iterate over list elementsfor element in integer_list:    print(element) # 1 2 3
Other operations: There are some additional operations that apply to most of the sequences such as max(), min(), count(), index(). Let’s see how you can use them with list objects;
# list of integersinteger_list = [1, 2, 3, 1, 8, 1]# max(), min()max(integer_list) # 8min(integer_list) # 1# count(), index()integer_list.index(3) # 2integer_list.index(1) # 0integer_list.count(1) # 3integer_list.count(9) # 0
Here are the remaining methods you can use with the list-objects.
append(element): Adds a single element to the end of the list. This method modifies the original list and does not return a new list;
# list of integersinteger_list = [1, 2, 3]# append()integer_list.append(5)print(integer_list)# output# [1, 2, 3, 5]
extend(other_list): Adds the elements in other_list to the end of the original list;
integer_list = [1, 2, 3]string_list = ['data', 'science']# extend()integer_list.extend(string_list) #[1, 2, 3, 'data', 'science']integer_list.extend('rocks') #[1, 2, 3, 'data', 'science', 'r', 'o', 'c', 'k', 's']
insert(index, element): Inserts a single element at the given index and shifts the elements after to the right.
string_list = ['data', 'science']# insert()string_list.insert(1,'-') #['data', '-', 'science']string_list.insert(3,'rocks') #['data', '-', 'science', 'rocks']
sort(key=None, reverse=False) — Sorts the given list, does not return a new list object. sort() function sort the list in ascending order. You can provide reverser = True if you need to sort the list in descending order. You can also define a custom sorting order by creating a function to define which element of list items to be used as a key for ordering. As you can see in the below example, second_char(string_element) function returns the second character of each string element in the original list. The list then sorted based on the return value of the second_char function.
integer_list = [1, 2, 3, 1, 8, 1]# sort()integer_list.sort()print(integer_list)integer_list.sort(reverse=True)print(integer_list)# custom sortstring_list = ['science', 'rocks', 'data']def second_char(string_element):  return string_element[1]string_list.sort(key=second_char, reverse=False)print(string_list)
reverse() — Reverse simply reverse the list order. This function does not return a new list.
remove(element): Finds the first instance of the given element and removes it. Throws ValueError, if the given element is not present.
integer_list = [1, 2, 3, 1, 8, 1]# remove()integer_list.remove(1)print(integer_list) #[2, 3, 1, 8, 1]integer_list.remove(1)print(integer_list) #[2, 3, 8, 1]integer_list.remove(2)print(integer_list) #[3, 8, 1]integer_list.remove(9)  #ValueError: list.remove(x): x not in list
pop(index): Removes and returns the element at the given index. Removes and returns the last element if the index is no provided.
integer_list = [1, 2, 3, 4, 5, 6, 7]# pop()integer_list.pop(3)#returns 4print(integer_list)#[1, 2, 3, 5, 6, 7]integer_list.pop() #returns 7print(integer_list)#[1, 2, 3, 5, 6]
List comprehensions provide an elegant and concise way of creating lists. The syntax for the list comprehensions;
new_list = [expression for member in iterable(if conditional)]integer_list = [x for x in range(10) if x % 2 == 0  if x % 5 == 0]print(integer_list) #[0, 10, 20, 30]
As data structures are fundamental parts of our programs, it is really important to have a solid understanding of Python lists to create efficient programs.
I explained why and when to use the lists, some of the key takeaways are listed below;
Fundamental characteristics of Python lists are as follows; They are mutable, ordered, dynamic and array type (sequence) data structures.
We can use the lists when we want to store a collection of heterogeneous objects in a single data structure.
New elements can be added or removed in runtime. Lists are not fixed-size objects, so they are called dynamic data structures.
The order in which you specify the elements when you define a list is maintained so lists are ordered.
Thank you for reading!