Skip to content

List comprehensions

This short sub-section provides additional information on list comprehensions as they are used commonly in Python code due to their conciseness. For a complete description of list comprehensions, see the official Python documentation here which describes list comprehensions as:

List comprehensions provide a concise way to create lists. 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.

In Smash, list comprehension were introduced in the following places:

  • check_for_collisions() in Step 6: Destroying the blocks
  • ParticleExplosion.update() in Step 9: Add particle effects, Explosion particle effect

We will now look at each of those list comprehensions, breaking them down into more traditional loops so you can see more clearly how they work.

check_for_collisions() in Step 6: Destroying the blocks

The following excerpt shows the section of code with the list comprehension in check_for_collisions().

blocks_to_destroy = [block for block in blocks if ball.collide(block.bounding_box)]

This type of list comprehension is very common. You will see these regularly in Python code. What this code is doing is looping over every block in the blocks list. Each block is checked for whether it collides with the ball. If it does, it is added to a new list. The new list is is then assigned to the blocks_to_destroy variable.

The above single line list comprehension is equivalent to the following 4 lines of code:

blocks_to_destroy = []

for block in blocks:

    if ball.collide(block.bounding_box)

        blocks_to_destroy.append(block)

Can you see how each of the first 3 lines of code maps to the list comprehension?

A new example

We will now work through a new example that you can run in Replit to demonstrate the functionality of a list comprehension and experiment with it. We will first start with the code using a plain ld loop and then turn it into a list comprehension. The example we will be using is to select all numbers that are negative from a list of 10 numbers.

Type in the following code and run it.


numbers = [1, -1, 30, -400, -5, 13, 17, -27, 0, 1000]

negative_numbers = []

for number in numbers:

    if number < 0:

        negative_numbers.append(number)

print(negative_numbers)

You should see the following output:

[-1, -400, -5, -27]

Using the same pattern as before, the list comprehension can be build from the parts of the loop code. The entire section of code above can be condensed into:

numbers = [1, -1, 30, -400, -5, 13, 17, -27, 0, 1000]

negative_numbers = [number for number in numbers if number < 0]

print(negative_numbers)

Experiments

Change the code so that it only selects positive numbers.

Change the code so that it only selects numbers greater than 100.

Change the code so that is only selects numbers whose magnitude is greater than 20. To get the magnitude you can use the built in function abs(). For information see the following resources:

ParticleExplosion.update() in Step 9: Add particle effects

The following except shows the section of code that creates the particles list in the __init__() method as well as the list comprehension in the update() method of the ParticleExplosion class.

GRAVITY = 60

class ParticleExplosion:
    def __init__(self, pos, lifetime, colour):
        self.particles = [(pos[0], pos[1], randint(-90, 90), randint(-90, 90)) for _ in range(30)]

    def update(self, dt):
        self.particles = [(particle[0] + (particle[2] * dt),
                           particle[1] + (particle[3] * dt), particle[2],
                           particle[3] + (GRAVITY * dt))
                          for particle in self.particles]

This looks more complicated than the first example but it really isn't. We can remove some of the code to make an more easily testable piece of code as follows (note, this code reduces the number of particles from 30 to 5):

from random import randint

GRAVITY = 60
x = 10
y = 20
pos = (x, y)
particles = [(pos[0], pos[1], randint(-90, 90), randint(-90, 90)) for _ in range(5)]
print(particles)

dt = 1
particles = [(particle[0] + (particle[2] * dt),
              particle[1] + (particle[3] * dt), particle[2],
              particle[3] + (GRAVITY * dt))
             for particle in particles]
print(particles)

Run the above code in Replit and you will get output similar to this below:

[(10, 20, 32, -66), (10, 20, -69, -74), (10, 20, -85, 56), (10, 20, -90, -68), (10, 20, -78, -39)]
[(42, -46, 32, -6), (-59, -54, -69, -14), (-75, 76, -85, 116), (-80, -48, -90, -8), (-68, -19, -78, 21)]

This is the list comprehension:

particles = [(particle[0] + (particle[2] * dt),
              particle[1] + (particle[3] * dt), particle[2],
              particle[3] + (GRAVITY * dt))
             for particle in particles]

Now lets break this down to a standard loop as we did before:

new_particles = []
for particle in particles:
    x, y, vx, vy = particle
    new_x = x + vx * dt
    new_y = y + vy * dt
    new_vy = vy + GRAVITY * dt
    new_particles.append((new_x, new_y, vx, new_vy))

particles = new_particles
print(particles)

Because the above code uses intermediate variables, each of the items in the particle tuple is given a name of x, y, vx or vy which represents the x and y co-ordinate of the particle, the horizontal speed of the particle (vx) and the vertical speed of the particle (vy).

What this code demonstrates that may not have been obvious in the first example above is the limitation that you should not mutate the contents of a list when looping over them. This is why the modified particles are first added to the new_particles list and then at the end of the code the new_particles list is assigned to particles. List comprehensions do not have this limitation.

For more information on tuples, see:

Experiments

Replace the following line of code:

new_particles.append((new_x, new_y, vx, new_vy))

With:

particles.append((new_x, new_y, vx, new_vy))

And remove the particles = new_particles line of code.

Run your program. What happens?