Python Book
πŸ‡ΊπŸ‡¦ Stand with UkraineπŸŽ“Training Suite
  • Book overview
  • Notes about this book
  • 1. Introduction to Python
    • What is Python
    • Basic syntax
    • Objects in Python
    • Python overview
    • Installation, IDEs etc.
    • ipython
    • Sources for self-learning
  • 2. Strings and numbers
    • Getting help
    • Introspection
    • Basic types
    • None object
    • Numbers
    • Strings
    • Unicode
    • String Formatting
    • Regular expressions
    • Sources for self-learning
  • 3. Containers
    • Data Structures
    • Lists
    • Tuples
    • Dictionaries
    • Sets
    • Conditions
    • Loops
    • Additional modules
    • Sources for self-learning
  • 4. Functions
    • Functions
    • Scopes of visibility
    • Generators
    • Lambdas
    • Type hints
    • Function internals
    • Sources for self-learning
  • 5. Functional Programming
    • Builtins
    • Iterable
    • Iterator
    • Functional Programming
    • Functools
    • Comprehensions
    • Additional modules
    • Sources for self-learning
  • 6. Code Styling
    • Zen of Python
    • Lint
    • PEP 8
    • Modules
    • Packages
    • Sources for self-learning
  • 7. OOP
    • OOP Basics
    • Code design principles
    • Classes
    • Method Resolution Order
    • Magic attributes and methods
    • Super
    • Sources for self-learning
  • 8. Decorators, Exceptions
    • Decorators
    • Exceptions
    • Sources for self-learning
  • 9. Testing
    • Basic Terminology
    • Testing theory
    • Dev unit testing vs QA automated testing
    • Best Practices
    • Doctest
    • Unittest
    • Test Runners
    • Pytest
    • Nose
    • Continuous Integration
  • 10. System Libs
    • Working with files
    • System libraries
    • Subprocess
    • Additional CLI libraries
Powered by GitBook
On this page
  • Main methods
  • Examples
  • Sorting
  • Appending, extending
  • Removing
  • Other examples
  • List slices
  • Slice obtaining
  • Slice deletion
  • Slice assignment
  • List comprehensions
  • Deep/shallow copy
  • Shallow copy of lists
  • Shallow copy problems
  • Deep copy
  • Self-referencing list
  • Lists for heavy calculations

Was this helpful?

Edit on Git
  1. 3. Containers

Lists

List is ordered mutable sequence of ANY objects.

This means that it is allowed to change it's elements after creation: adding new, changing/removing existing etc.

Ways to create a list:

  • []

  • list()

  • [1, 2, 3, [10, 20], 5]

Main methods

πŸͺ„ Code:

some_list = [4, 8, 15, 16, 23, 42]
print(dir(some_list))  # Again all methods

πŸ“Ÿ Output:

['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Actually beside magical methods (containing __) there are not so much methods left

Worth to note that most methods are not returning anything - they are just directly changing the list itself because it is mutable object.

List methods

Method(s)
Description

len(some_list)

Return int - length of the list

reverse()

Reverse order of elements of the list in place

sort()

Sort elements of the list in place

append(x)

Add new element x to the end of the list

extend(iterable)

Append elements of iterable to the end of the list

pop()

Return and remove the last element from the list

pop(i)

Return and remove element by index i from the list

Method(s)
Description

some_list[i]

Return (or assign) element by index i (int)

some_list[i:j]

Return new list - slice from element by index i till j-1

some_list[i:j:z]

Return new list - slice with step z

del some_list[3]

Remove element of index 3 from list some_list

del some_list[2:4]

Remove part (starting from index 2 till 3) from list some_list

2 in some_list

Return True/False - is object 2is part of list some_list?

some_list + ["a", "b"]

Return new list - a sum of merged lists

Method(s)
Description

count(x)

Return number of object x found in a list

index(x)

Return first index of object x in list or raise ValueError exception if not found

Last two we saw in strings as well.

Examples

Lists are indexable, starting at 0

πŸͺ„ Code:

print(some_list)
print(some_list[0])

πŸ“Ÿ Output:

[4, 8, 15, 16, 23, 42]
4

πŸͺ„ Code:

some_list[2]

πŸ“Ÿ Output:

15

πŸͺ„ Code:

some_list[-1]

πŸ“Ÿ Output:

42

πŸͺ„ Code:

print(some_list)
print(len(some_list))

πŸ“Ÿ Output:

[4, 8, 15, 16, 23, 42]
6

πŸͺ„ Code:

some_list.reverse()
print(some_list)

πŸ“Ÿ Output:

[42, 23, 16, 15, 8, 4]

πŸͺ„ Code:

some_list.sort()
print(some_list)

πŸ“Ÿ Output:

[4, 8, 15, 16, 23, 42]

πŸͺ„ Code:

new_list = some_list + ["a", "b", "c"]
print(new_list)

πŸ“Ÿ Output:

[4, 8, 15, 16, 23, 42, 'a', 'b', 'c']

Sorting

Sorting of the list with elements of different types in Python 3 is forbidden (yields an TypeError exception about unsupported < operation).

πŸͺ„ Code:

new_list 
new_list.sort()

πŸ“Ÿ Output:

---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Input In [282], in <cell line: 2>()
      1 new_list 
----> 2 new_list.sort()


TypeError: '<' not supported between instances of 'str' and 'int'

But we can workaround this by specifying your own sorting function (as argument key) which should return some values of the same type which Python will be sorting instead of real values.

πŸͺ„ Code:

new_list.sort(key=str) # Here we are sorting in fact by string representations
print(new_list)

πŸ“Ÿ Output:

[15, 16, 23, 4, 42, 8, 'a', 'b', 'c']

Appending, extending

πŸͺ„ Code:

some_list.append(1000)
some_list

πŸ“Ÿ Output:

[4, 8, 15, 16, 23, 42, 1000]

πŸͺ„ Code:

some_list = [4, 8, 15, 16, 23, 42, 1000]
some_list.extend([1000, [10, 10, [20, 20]], some_list])
some_list

πŸ“Ÿ Output:

[4, 8, 15, 16, 23, 42, 1000, 1000, [10, 10, [20, 20]], [...]]

By extending nested list Python will treat all nested as elements just as other types

πŸͺ„ Code:

some_list.extend([ 0, [1,2], [3, 4]])
some_list

πŸ“Ÿ Output:

[4,
 8,
 15,
 16,
 23,
 42,
 1000,
 1000,
 [10, 10, [20, 20]],
 [...],
 0,
 [1, 2],
 [3, 4]]

Removing

πŸͺ„ Code:

some_list.pop()

πŸ“Ÿ Output:

[3, 4]

πŸͺ„ Code:

some_list.pop(0)

πŸ“Ÿ Output:

4
del some_list[0]
del some_list[3:6]

πŸͺ„ Code:

some_list

πŸ“Ÿ Output:

[15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]]

Other examples

We saw very similar examples for strings - because strings are sequence too. That is the core idea (called ducktyping) in Python - the same behavior even if the types are different.

πŸͺ„ Code:

some_list * 2

πŸ“Ÿ Output:

[15,
 16,
 23,
 [10, 10, [20, 20]],
 [15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]],
 0,
 [1, 2],
 15,
 16,
 23,
 [10, 10, [20, 20]],
 [15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]],
 0,
 [1, 2]]

πŸͺ„ Code:

some_list[::-1]

πŸ“Ÿ Output:

[[1, 2],
 0,
 [15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]],
 [10, 10, [20, 20]],
 23,
 16,
 15]

πŸͺ„ Code:

some_list[:4]

πŸ“Ÿ Output:

[15, 16, 23, [10, 10, [20, 20]]]

πŸͺ„ Code:

some_list[2:]

πŸ“Ÿ Output:

[23,
 [10, 10, [20, 20]],
 [15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]],
 0,
 [1, 2]]

πŸͺ„ Code:

print (some_list)
some_list[-2]

πŸ“Ÿ Output:

[15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]]





0

πŸͺ„ Code:

(some_list * 4)[::4]

πŸ“Ÿ Output:

[15,
 [15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]],
 16,
 0,
 23,
 [1, 2],
 [10, 10, [20, 20]]]

IndexError exception is raised for non-existent index element

πŸͺ„ Code:

some_list[1000]

πŸ“Ÿ Output:

---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

Input In [297], in <cell line: 1>()
----> 1 some_list[1000]


IndexError: list index out of range

List slices

Slices of list object are a bit different to the slices of strings due to the fact that lists are mutable. Because of these additional operations are available:

  • slice deletion

  • slice assignment

Firstly, let's check regular list slice obtaining:

Slice obtaining

πŸͺ„ Code:

print (some_list)
some_list[2:4]

πŸ“Ÿ Output:

[15, 16, 23, [10, 10, [20, 20]], [...], 0, [1, 2]]





[23, [10, 10, [20, 20]]]

πŸͺ„ Code:

some_list[2:4] = ["a", "b", "c"]
some_list

πŸ“Ÿ Output:

[15, 16, 'a', 'b', 'c', [...], 0, [1, 2]]

πŸͺ„ Code:

some_list[:1] = [100, 100, 100]
some_list

πŸ“Ÿ Output:

[100, 100, 100, 16, 'a', 'b', 'c', [...], 0, [1, 2]]

Slice deletion

By using del operator on the slice it is possible to delete target part from the original list:

πŸͺ„ Code:

list_ = list(range(1, 6))
print("Before:", list_)

del list_[2:4]
print("After:", list_)

πŸ“Ÿ Output:

Before: [1, 2, 3, 4, 5]
After: [1, 2, 5]

Slice assignment

By assignining the slice to another iterable, the content of that iterable will be used to replace the target part of the original list:

πŸͺ„ Code:

list_ = list(range(1, 6))
print("Before:", list_)

list_[2:4] = ["A"]
print("After:", list_)

πŸ“Ÿ Output:

Before: [1, 2, 3, 4, 5]
After: [1, 2, 'A', 5]

List comprehensions

Fast, simple and "Pythonic" way to create a list

Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition

πŸͺ„ Code:

cool_list = [x*2 for x in range(10)]
cool_list

πŸ“Ÿ Output:

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

It's absolutely the same as:

πŸͺ„ Code:

cool_list = []
for x in range(10):
    cool_list.append(x*2)
cool_list

πŸ“Ÿ Output:

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

πŸͺ„ Code:

[x**2 + 2*x + 1 for x in range(10)]

πŸ“Ÿ Output:

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

πŸͺ„ Code:

# Random list (len till 15) from rand ints (till 10)
from random import randrange
[randrange(0, 10) for _ in range(randrange(3, 15))]

πŸ“Ÿ Output:

[9, 2, 2, 4, 6, 2]

πŸͺ„ Code:

[f"{x}-{y}" for x in [1, 2, 3] for y in "ABC"]

πŸ“Ÿ Output:

['1-A', '1-B', '1-C', '2-A', '2-B', '2-C', '3-A', '3-B', '3-C']

πŸͺ„ Code:

list_ = []
for x in [1, 2, 3]:
    for y in "ABC":
        list_.append(f"{x}-{y}")
print(list_)

πŸ“Ÿ Output:

['1-A', '1-B', '1-C', '2-A', '2-B', '2-C', '3-A', '3-B', '3-C']

The order of loops matters:

πŸͺ„ Code:

[f'{x}-{y}' for x in "abc" for y in range(1, 4)]

πŸ“Ÿ Output:

['a-1', 'a-2', 'a-3', 'b-1', 'b-2', 'b-3', 'c-1', 'c-2', 'c-3']

πŸͺ„ Code:

[f'{x}-{y}' for y in range(1, 4) for x in "abc"]

πŸ“Ÿ Output:

['a-1', 'b-1', 'c-1', 'a-2', 'b-2', 'c-2', 'a-3', 'b-3', 'c-3']

It's possible to create nested list and use conditions as well:

πŸͺ„ Code:

[(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]

πŸ“Ÿ Output:

[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

is equivalent to:

πŸͺ„ Code:

combs = []
for x in [1, 2, 3]:
    for y in [3, 1, 4]:
        if x != y:
            combs.append((x, y))
combs

πŸ“Ÿ Output:

[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Some "real-world" example:

[func(x, y) for x in list_x for y in list_y if check(x, y)]

Deep/shallow copy

πŸ”₯

Variables in Python (name bindings) are just points to an object in memory. By assigning one variable to another we make them point to the same object which may lead to various problems.

Operator is allows comparing by ids - shows is two variables pointing to the same object

πŸͺ„ Code:

a = [1, 2, 3]
b = a
print (a is b)
a[0] = "Surprise"
print ("b is", b)

πŸ“Ÿ Output:

True
b is ['Surprise', 2, 3]

Shallow copy of lists

Create completely new copy of the list - by creating a new list and adding elements one by one from old one

πŸͺ„ Code:

b = a.copy() # Pythonic way
b = list(a) # Almost Pythonic way
b = a[:] # hacker way

print (b is a)
a[0] = "Oh!"
print ("a is:", a)
print ("b is:", b)

πŸ“Ÿ Output:

False
a is: ['Oh!', 2, 3]
b is: ['Surprise', 2, 3]

πŸͺ„ Code:

b = a[:]  # Hackish way
a[0] = "New"
print ("b is:", b)
print(a)

πŸ“Ÿ Output:

b is: ['Oh!', 2, 3]
['New', 2, 3]

Shallow copy problems

If list has mutable object deeper than on the very first nesting level - shallow copy will cause similar problems as before.

πŸͺ„ Code:

a = [[1, 2], 3]
b = list(a)
a[0][0] = "Surprise!"
print (a is b)
print ("b is:", b)

πŸ“Ÿ Output:

False
b is: [['Surprise!', 2], 3]

Deep copy

There is a special standard module for copying - copy

πŸͺ„ Code:

import copy
a = [[1, 2], 3]

# Shallow copy
b = copy.copy(a)

# Deep copy
c = copy.deepcopy(a)
a[0][0] = "Surprise!"
print ("b is:", b)
print ("c is:", c)

πŸ“Ÿ Output:

b is: [['Surprise!', 2], 3]
c is: [[1, 2], 3]

Self-referencing list

List is seqence-like object that contains pointers to other objects which are treated as it's elements. That's why it's possible to add list to itself as element!

πŸͺ„ Code:

a = [1, 2, 3]
a.append(a)
print(a)
print(a[-1][-1][-1][-1][-1][-1][-1])

πŸ“Ÿ Output:

[1, 2, 3, [...]]
[1, 2, 3, [...]]

In this case when we delete it - Python garbage collector won't delete from memory - because the number of reference to this object is not null

Usual Python console:

πŸͺ„ Code >>> and πŸ“Ÿ Output:

>>> a = [1, 2, 3]
>>> import sys
>>> sys.getrefcount(a)
2
>>> a.append(a)
>>> sys.getrefcount(a)
3

You need to be aware of such cases.

Lists for heavy calculations

If you use list for complex math calculations - consider using numpy.

They are a bit slower to create:

πŸͺ„ Code:

import numpy
array_ = numpy.array(list(range(1000)))
list_ = list((range(1000)))

%timeit 999 in list_
%timeit 999 in array_

πŸ“Ÿ Output:

15.3 Β΅s Β± 99.8 ns per loop (mean Β± std. dev. of 7 runs, 100,000 loops each)
20.4 Β΅s Β± 373 ns per loop (mean Β± std. dev. of 7 runs, 10,000 loops each)

But they are much faster (x5 in the following example) for a vector operations:

πŸͺ„ Code:

import math

%timeit list(map(math.sqrt, list_))
%timeit numpy.sqrt(array_)

πŸ“Ÿ Output:

109 Β΅s Β± 1.48 Β΅s per loop (mean Β± std. dev. of 7 runs, 10,000 loops each)
21.3 Β΅s Β± 317 ns per loop (mean Β± std. dev. of 7 runs, 10,000 loops each)

## Complexity of operations

πŸ”₯

Time-complexity (aka "Big O" or "Big Oh") of various operations in current CPython.

Generally, 'n' is the number of elements currently in the container. 'k' is either the value of a parameter or the number of elements in the parameter.

Operation
Average Case
Amortized Worst Case

Copy

O(n)

O(n)

Append

O(1)

O(1)

Insert

O(n)

O(n)

Get Item

O(1)

O(1)

Set Item

O(1)

O(1)

Delete Item

O(n)

O(n)

Operation
Average Case
Amortized Worst Case

Iteration

O(n)

O(n)

Get Slice

O(k)

O(k)

Del Slice

O(n)

O(n)

Set Slice

O(k+n)

O(k+n)

Extend

O(k)

O(k)

Sort

O(n log n)

O(n log n)

Multiply

O(nk)

O(nk)

x in s

O(n)

min(s), max(s)

O(n)

Get Length

O(1)

O(1)

PreviousData StructuresNextTuples

Last updated 2 years ago

Was this helpful?

As we can see from section, list has fast (complexity is O(1)) such methods as append and pop which work with right end of the structure. So this is a FILO structure - "stack". So, if we want to achieve maximum performance we should use list as stack (and not use insert(0, x) and pop(0) methods).

Complexity of operations