Pythonic constructions

Accessing last elements

In Python, we can access the last elements of a list with index len(L) - 1, len(L) - 2, etc. This is not Pythonic.

L = [1, 2, 1, 3, 4, 5, 8, 42]
L[len(L)-1]

This is the Pythonic option:

L = [1, 2, 1, 3, 4, 5, 8, 42]
L[-1]

Of course, the same trick works for tuples:

t = (1, 2, 1, 3, 4)
t[-1]

Slices

We can obtain slices of a list like this:

L = [1, 2, 1, 3, 4, 5, 8, 42]
L[2:5]
[1, 4]

Quiz

  • What are the outputs of the following programs?
L = [1, 2, 1, 3, 4]
L[2] = 500
L
[1, 2, 500, 3, 4]
L = [1, 2, 1, 3, 4]
M = L[2:4]
M[0] = 500
M
[500, 3]
L = [1, 2, 1, 3, 4]
M = L[2:4]
M[0] = 500
L
[1, 2, 1, 3, 4]

We can obtain a slice of a tuple like this:

T = (1, 2, 8, 45, 2)
T[2:5]
(8, 45, 2)
T[:2]
(1, 2)
T[2:]
(8, 45, 2)

More generally, the syntax is L[start:stop:step], where the element at index stop is excluded.

L = list(range(0, 21))
L[1:8:2]
L = list(range(0, 21))
L[-3::-3]
L = list(range(0, 21))
L[-3:0:-3]

Sum of elements

Python provides sum to sum elements of a list, a tuple:

sum([1, 2, 5])
8
sum((1, 2, 5))
8
sum({1, 1, 2, 5})

List/set comprehensions

This code is super ugly:

squares = []
for i in range(10):
    squares.append(i**2)
squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Instead, we can use list comprehension. See https://peps.python.org/pep-0202/

[i*2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

List comprehension comes from the language SETL (SET Language) in 1969, where we can write:

{x: x in {2..n} | forall y in {2..x-1} | x mod y /= 0}

It is also present in Haskell:

s = [x | x <- [0..], x^2 > 3].
A = [[0 for i in range(10)] for j in range(5)]
A
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
A = [[0 for i in range(10)] for j in range(5)]
A[0].append(1)
A
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
[i for i in range(20) if i%2 == 0]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Be careful with *!

[[0]*10]*5
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
A = [[0]*10]*5
A[0][0] = 1
A
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

Here is an example of set comprehension.

{c for c in "Hello world!"}
{' ', '!', 'H', 'd', 'e', 'l', 'o', 'r', 'w'}

Here is an example of dictionary comprehension.

s = "hello world!"
{c: s.count(c) for c in s}
{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1}

Generators by comprehension

We can also write a sort of list by comprehension but the list itself is never created.

(i*2 for i in range(100))
<generator object <genexpr> at 0x7fd100179eb0>

They can even contain an infinite number of objects:

import itertools
(i*2 for i in itertools.count())
<generator object <genexpr> at 0x7fd100127120>

Unpacking tuples

point = (42, 2)
x, y = point
print(x)
42
x, y = [10, 2]
x
10
x, y = [1, 2, 3]
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

Cell In[28], line 1
----> 1 x, y = [1, 2, 3]


ValueError: too many values to unpack (expected 2)
x, _, y = [1, 2, 3]
y
3

Pattern matching

Here is an example of real pattern matching.

i=0
match i:
    case 0: print("zero")
    case _: print("non zero")

Functional programming

Python has filter, map, etc. even if they are not very useful because of list/set comprehension.

filter(lambda x: x % 2 == 0, [1, 2, 4, 6])
<filter at 0x7fd1002eafa0>
for x in filter(lambda x: x % 2 == 0, [1, 2, 4, 6]):
    print(x)
2
4
6
map(lambda x: 2*x, [1, 2, 3])
<map at 0x7fd0e37be6d0>
list(map(lambda x: x**2, [1, 2, 3]))
[1, 4, 9]