Flower in sunrise to illustrate a Python turtle animation using named tuples

Sunrise: A Python Turtle Animation Using Named Tuples

You may think the Python turtle module isn’t useful for anything. “It’s just for kids”, is what many think of it. However, this module allows you to explore and experiment with many areas of Python. In this article, you’ll write a Python turtle animation of a sunrise scene using named tuples:

As you work your way through this article, you’ll explore:

  • How to use Python named tuples
  • How to make gradual changes in colour and position in an animation

Don’t worry if you’ve never used Python’s turtle module. I’ll explain what you need to write this Python turtle animation.

I’ll assume you’re familiar with defining functions and Python’s built-in data structures. If you need to learn more about these topics or just refresh your memory, you can read Power-up Your Coding: Create Your Own Functions and Data, Data Types and Data Structures in The Python Coding Book.

Planning The Sunrise Python Turtle Animation

Let’s look at the steps needed to write this Python turtle animation:

  • You’ll need to write functions to draw the stars, the flower, and the sun. The colours will need to change. And in the case of the sun, its position will need to change, too
  • You’ll need to work out how much to change the colours of the items in each frame. You’ll also need to work out how much to move the sun in each frame
  • Finally, you’ll need to write the loop to run the animation

You’ll deal with lots of colours throughout the animation. You need to define the start and end colours for each item in your drawing. Each colour is made up of three components: red, green, and a blue.

This means you’ll start with six values for each item in your drawing: three to represent the starting colour and three to represent the final colour. You also need to change each item’s red, green, and blue components in each frame of the animation.

In the next section, you’ll look at what data structures you can use to deal with these requirements.

Using Python Named Tuples

Let’s start by looking at colours. You often see colours represented by three values showing how much red, green, and blue the colour is made up of. This is the RGB colour model. You’ll usually see these as numbers in the range from 0 to 255 as this represents 8-bit colour. If each component is represented by 8 bits, then there are 2^8 values, which is 256.

Therefore, white is represented by (255, 255, 255) as it consists of the maximum amount of red, green, and blue. Black is represented by (0, 0, 0). Other combinations of RGB values represent other colours.

You could represent a colour in Python by storing the three RGB values in a list. However, you’ll often see colours defined as tuples instead. Tuples are more suited for this as they’re immutable. A colour will always have three values, and the RGB values will not change for that colour:

>>> sky_colour = 0, 191, 255
>>> type(sky_colour)
<class 'tuple'>

The variable sky_colour is a tuple containing RGB values. This represents a light blue colour which you’ll use as the sky colour in the sunrise animation.

You’ll need to store two colours for each part of your drawing:

  • The first colour corresponds to the start of the animation
  • The second colour corresponds to the end of the animation

You can create another tuple for this:

>>> sky_colour = (
...     (0, 0, 0),
...     (0, 191, 255)
... )

>>> type(sky_colour)
<class 'tuple'>

>>> sky_colour[0]  # Start colour
(0, 0, 0)
>>> sky_colour[1][2]  # Blue value of end colour
255

The variable sky_colour is a 2-tuple. That’s a tuple that has two items. Each one of those items is a 3-tuple with the RGB values.

You can access the colour at the start of the animation using indexing: sky_colour[0]. You can also go deeper into the nesting, for example sky_colour[1][2] is the blue value of the colour at the end of the animation.

This is perfectly fine. However, using indexes can be confusing and lead to errors in the code since you’ll need to keep track of what each index represents.

Named Tuples

Instead, you can use named tuples. You can find Python’s named tuples in the collections module. Let’s see how you can create a named tuple to represent colour:

>>> from collections import namedtuple

>>> Colour = namedtuple("Colour", "red, green, blue")

>>> issubclass(Colour, tuple)
True
>>> Colour._fields
('red', 'green', 'blue')

You create a class called Colour, which is a subclass of tuple.

Colour has three fields which you set in the second argument when you call namedtuple().

You can now define sky_colour as you did earlier. You’ll start by defining only the end colour, for now. However, this time, you can use the named tuple class Colour rather than a standard tuple:

>>> sky_colour = Colour(0, 191, 255)
>>> sky_colour[1]
191
>>> sky_colour.green
191

sky_colour can still be accessed using indexes just as a standard tuple. But you can also access items using the field name, such as sky_colour.green. This can make your code more readable.

You can create another named tuple to define the start and end points of a range:

>>> RangeLimits = namedtuple("RangeLimits", "start, end")

>>> sky_colour = RangeLimits(
...     Colour(0, 0, 0),
...     Colour(0, 191, 255),
... )

>>> sky_colour[1][2]
255
>>> sky_colour.end.blue
255

This time, you define sky_colour as a RangeLimits named tuple. This named tuple contains two Colour named tuples.

Now, you can access the end colour’s blue value either by using sky_colour[1][2] or sky_colour.end.blue. Accessing values using field names is more readable in this case and less likely to lead to errors.

You may be thinking that a named tuple is similar to a dictionary. However, a named tuple is immutable, just like standard tuples. However, dictionaries are mutable.

You’ll read more about the differences between named tuples and dictionaries later in this article.

To learn more about named tuples, you can read the Real Python article Write Pythonic and Clean Code With namedtuple.

Setting Up The Sunrise Python Turtle Animation

You can start setting up the turtle animation by creating a window. You can create a script called sunrise.py:

# sunrise.py

import turtle

width = 1200
height = 800

# Set up the window for the animation
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")

turtle.done()

The variable sky represents the screen object with the width and height you choose. You can also add a title to the window.

You also call sky.tracer(0), allowing you to control when things are displayed in your animation. When you set the tracer to 0, you’ll need to call sky.update() to update the drawing on the window. There’s nothing being drawn at the moment, so there’s no call to sky.update() yet.

When you run this code, you’ll see a blank window displayed. The turtle.done() call keeps the program from terminating.

Note: If you’re using a web-based editor to code in Python, you may find that sky.setup() and sky.title() raise errors. These methods are not present in the turtle version used by most online editors.

Adding The Named Tuple Classes

In the earlier section introducing named tuples, you created the Colour and the RangeLimits named tuple classes. You can use these in the animation code:

# sunrise.py

import turtle

from collections import namedtuple

width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)

# Set up the window for the animation
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")
sky.bgcolor(sky_colour.start)

turtle.done()

There is one difference in this script. The colour values are divided by 255. The default colour mode in the turtle module represents colours as floats in the range from 0 to 1 rather than integers in the range 0 to 255.

You also call sky.bgcolor() which changes the background colour. The output is now a window with a black background since sky_colour.start is black. This is the first colour in the sky_colour named tuple.

Note: If you’re using a web-based editor to code in Python, you should add sky.colormode(1) to set this as the default mode.

Calculating The Colour Change Needed In Each Animation Frame

To create an animation, you’ll need a loop. Each iteration of the loop represents a frame. You could control your animation based on time. However, I’ll choose a simpler route for this article and set the length of the iteration based on the number of frames rather than time.

You can create a variable called number_of_steps to determine how many frames you want your animation to have. You can also define a function to calculate how much you’ll need to change a colour in each animation frame:

# sunrise.py

import turtle

from collections import namedtuple

number_of_steps = 500
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

# Set up the window for the animation
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")
sky.bgcolor(sky_colour.start)

# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)

turtle.done()

The function calculate_colour_change() has three parameters:

  • start is the colour at the start of the animation
  • end is the colour at the end of the animation
  • n_steps is the number of steps in the animation

You use type hinting to make the code more readable and make it clear that start and stop are objects of the class Colour. This is the named tuple class you created.

You need to work out the step sizes required for the red, green, and blue components separately to determine how to change the colours one frame at a time.

The function calculate_colour_change() returns an object of type Colour. Technically, the return value is not a colour but the steps needed for the red, green, and blue components to make a change to the colour. However, the Colour class works perfectly fine for this, too.

You call this function to work out the steps needed to change the sky colour from black to sky blue in 500 frames.

Adding The Animation Loop

Now, you can add the animation loop. Since you’re determining the animation’s length by choosing the number of frames, you can use a for loop as the main loop:

# sunrise.py

import turtle

from collections import namedtuple

number_of_steps = 500
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

# Set up the window for the animation
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")
sky.bgcolor(sky_colour.start)

# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)

current_sky_colour = sky_colour.start

for _ in range(number_of_steps):
    current_sky_colour = Colour(
        current_sky_colour.red + sky_colour_steps.red,
        current_sky_colour.green + sky_colour_steps.green,
        current_sky_colour.blue + sky_colour_steps.blue,
    )
    # Change the background to use the new colour
    sky.bgcolor(current_sky_colour)

    sky.update()

turtle.done()

You create a variable called current_sky_colour which is initially equal to the start colour. In the for loop, you modify this colour’s red, green, and blue components by adding the steps you calculated. Finally, you update the screen’s background colour.

You also call sky.update() since you want to update the display once per frame.

This code creates the following animation:

The sky colour changes gradually from black to sky blue. You can control the speed of the animation by changing the value of number_of_steps. The more steps you use, the slower the sky’s change of colour will be.

Updating A Named Tuple?!

You may recall that you used a named tuple to represent the start and end colours because these do not change. It made sense to use an immutable data type.

However, current_sky_colour needs to be updated every frame. Since a Colour object is a named tuple, and therefore immutable, you have to overwrite the named tuple each time.

For the time being, I’ll keep using the named tuple class Colour for current_sky_colour and for similar variables linked to other parts of the drawings, which you’ll add soon.

You’ll revisit this point towards the end of this article. We’ll discuss whether this is the best approach for the colours that are changing in every frame.

Adding A Flower To The Scene

The hard work is done. Now, if you want to add more items to the scene, you can follow the same pattern as you’ve done with the background colour.

You can start by adding a flower with a stem. There are three components of the flower which have different colours:

  • The petals
  • The centre of the flower. This is called the pistil, but I’ll just refer to it as the centre of the flower!
  • The stem

You can add colours for all of these and work out the steps needed in each frame. Next, you create Turtle objects for the flower and the stem and use Turtle methods to hide the “pen” that’s doing the drawing, turn it, and change the size of the lines it draws:

# sunrise.py

import turtle

from collections import namedtuple

number_of_steps = 500
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)
petal_colour = RangeLimits(
    Colour(50 / 255, 50 / 255, 50 / 255),
    Colour(138 / 255, 43 / 255, 226 / 255),
)
flower_centre_colour = RangeLimits(
    Colour(30 / 255, 30 / 255, 30 / 255),
    Colour(255 / 255, 165 / 255, 0 / 255),
)
stem_colour = RangeLimits(
    Colour(15 / 255, 15 / 255, 15 / 255),
    Colour(34 / 255, 139 / 255, 34 / 255),
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

# Set up the window for the animation
# Sky
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")
sky.bgcolor(sky_colour.start)

# Flower and Stem
flower = turtle.Turtle()
flower.hideturtle()
stem = turtle.Turtle()
stem.hideturtle()
stem.right(90)
stem.pensize(10)

def draw_flower(petal_col, flower_centre_col, stem_col):
    stem.clear()
    stem.color(stem_col)
    stem.forward(height / 2)
    stem.forward(-height / 2)

    flower.clear()
    flower.color(petal_col)
    # Draw petals
    for _ in range(6):
        flower.forward(100)
        flower.dot(75)
        flower.forward(-100)
        flower.left(360 / 6)
    # Draw centre of flower
    flower.color(flower_centre_col)
    flower.dot(175)

# Draw the initial flower using the starting colours
draw_flower(
    petal_colour.start,
    flower_centre_colour.start,
    stem_colour.start,
)

####
# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)
petal_colour_steps = calculate_colour_change(
    petal_colour.start, petal_colour.end, number_of_steps
)
flower_centre_colour_steps = calculate_colour_change(
    flower_centre_colour.start,
    flower_centre_colour.end,
    number_of_steps,
)
stem_colour_steps = calculate_colour_change(
    stem_colour.start, stem_colour.end, number_of_steps
)

####
# Start animation
current_sky_colour = sky_colour.start
current_petal_colour = petal_colour.start
current_flower_centre_colour = flower_centre_colour.start
current_stem_colour = stem_colour.start

for _ in range(number_of_steps):
    # Sky
    current_sky_colour = Colour(
        current_sky_colour.red + sky_colour_steps.red,
        current_sky_colour.green + sky_colour_steps.green,
        current_sky_colour.blue + sky_colour_steps.blue,
    )
    # Change the background to use the new colour
    sky.bgcolor(current_sky_colour)

    # Flower and Stem
    current_petal_colour = Colour(
        current_petal_colour.red + petal_colour_steps.red,
        current_petal_colour.green + petal_colour_steps.green,
        current_petal_colour.blue + petal_colour_steps.blue,
    )
    current_flower_centre_colour = Colour(
        current_flower_centre_colour.red
        + flower_centre_colour_steps.red,
        current_flower_centre_colour.green
        + flower_centre_colour_steps.green,
        current_flower_centre_colour.blue
        + flower_centre_colour_steps.blue,
    )
    current_stem_colour = Colour(
        current_stem_colour.red + stem_colour_steps.red,
        current_stem_colour.green + stem_colour_steps.green,
        current_stem_colour.blue + stem_colour_steps.blue,
    )

    # Draw the flower again with the new colours
    draw_flower(
        current_petal_colour,
        current_flower_centre_colour,
        current_stem_colour,
    )

    sky.update()

turtle.done()

You define draw_flower() which draws the stem and flower by turning and moving the turtles and changing their colours. The function also clears the drawing from the previous frame when you call stem.clear() and flower.clear().

You calculate the steps needed to change the colours in every frame and set the initial colours like you did for the sky. You change the current colours in the animation loop and re-draw the flower in every frame.

The animation now looks like this:

Adding Stars To The Scene

Next, you can add stars in random positions on the screen. Since you’ll need to re-draw the stars in every frame, you can generate random star positions and sizes and store them so that you can use the same values each time you need to draw the stars. This is preferable to creating a new turtle for each star which can slow down the animation.

Dealing with the stars’ colour change follows the same pattern as with the sky and flower:

# sunrise.py

import random
import turtle

from collections import namedtuple

number_of_steps = 500
number_of_stars = 200
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)
petal_colour = RangeLimits(
    Colour(50 / 255, 50 / 255, 50 / 255),
    Colour(138 / 255, 43 / 255, 226 / 255),
)
flower_centre_colour = RangeLimits(
    Colour(30 / 255, 30 / 255, 30 / 255),
    Colour(255 / 255, 165 / 255, 0 / 255),
)
stem_colour = RangeLimits(
    Colour(15 / 255, 15 / 255, 15 / 255),
    Colour(34 / 255, 139 / 255, 34 / 255),
)
star_colour = RangeLimits(
    Colour(1, 1, 1),
    sky_colour.end,
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

# Set up the window for the animation
# Sky
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")
sky.bgcolor(sky_colour.start)

# Flower and Stem
flower = turtle.Turtle()
flower.hideturtle()
stem = turtle.Turtle()
stem.hideturtle()
stem.right(90)
stem.pensize(10)

def draw_flower(petal_col, flower_centre_col, stem_col):
    stem.clear()
    stem.color(stem_col)
    stem.forward(height / 2)
    stem.forward(-height / 2)

    flower.clear()
    flower.color(petal_col)
    # Draw petals
    for _ in range(6):
        flower.forward(100)
        flower.dot(75)
        flower.forward(-100)
        flower.left(360 / 6)
    # Draw centre of flower
    flower.color(flower_centre_col)
    flower.dot(175)

# Draw the initial flower using the starting colours
draw_flower(
    petal_colour.start,
    flower_centre_colour.start,
    stem_colour.start,
)

# Stars
stars = turtle.Turtle()
stars.hideturtle()
stars.penup()

# Generate pairs of coordinates for the star positions
star_positions = tuple(
    (
        random.randint(-width // 2, width // 2),
        random.randint(-width // 2, width // 2),
    )
    for _ in range(number_of_stars)
)
# …and size for the stars
star_sizes = tuple(
    random.randint(2, 8) for _ in range(number_of_stars)
)

def draw_stars(colour):
    stars.clear()
    stars.color(colour)
    for position, size in zip(star_positions, star_sizes):
        stars.setposition(position)
        stars.dot(size)

# Draw the initial stars using the starting colour
draw_stars(star_colour.start)

####
# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)
petal_colour_steps = calculate_colour_change(
    petal_colour.start, petal_colour.end, number_of_steps
)
flower_centre_colour_steps = calculate_colour_change(
    flower_centre_colour.start,
    flower_centre_colour.end,
    number_of_steps,
)
stem_colour_steps = calculate_colour_change(
    stem_colour.start, stem_colour.end, number_of_steps
)
star_colour_steps = calculate_colour_change(
    star_colour.start, star_colour.end, number_of_steps
)

####
# Start animation
current_sky_colour = sky_colour.start
current_petal_colour = petal_colour.start
current_flower_centre_colour = flower_centre_colour.start
current_stem_colour = stem_colour.start
current_star_colour = star_colour.start

for _ in range(number_of_steps):
    # Sky
    current_sky_colour = Colour(
        current_sky_colour.red + sky_colour_steps.red,
        current_sky_colour.green + sky_colour_steps.green,
        current_sky_colour.blue + sky_colour_steps.blue,
    )
    # Change the background to use the new colour
    sky.bgcolor(current_sky_colour)

    # Stars
    current_star_colour = Colour(
        current_star_colour.red + star_colour_steps.red,
        current_star_colour.green + star_colour_steps.green,
        current_star_colour.blue + star_colour_steps.blue,
    )

    draw_stars(current_star_colour)

    # Flower and Stem
    current_petal_colour = Colour(
        current_petal_colour.red + petal_colour_steps.red,
        current_petal_colour.green + petal_colour_steps.green,
        current_petal_colour.blue + petal_colour_steps.blue,
    )
    current_flower_centre_colour = Colour(
        current_flower_centre_colour.red
        + flower_centre_colour_steps.red,
        current_flower_centre_colour.green
        + flower_centre_colour_steps.green,
        current_flower_centre_colour.blue
        + flower_centre_colour_steps.blue,
    )
    current_stem_colour = Colour(
        current_stem_colour.red + stem_colour_steps.red,
        current_stem_colour.green + stem_colour_steps.green,
        current_stem_colour.blue + stem_colour_steps.blue,
    )

    # Draw the flower again with the new colours
    draw_flower(
        current_petal_colour,
        current_flower_centre_colour,
        current_stem_colour,
    )

    sky.update()

turtle.done()

The starting colour of the stars is white. However, you need to match the stars’ end colour to the sky’s end colour so that the stars ‘blend’ into the sky background when the sunrise is complete.

You use generator expressions to create the pairs of star coordinates and the star sizes and then convert these to tuples. If you’re familiar with list comprehensions, you may wonder why you couldn’t use the same syntax but replace the square brackets [ ] with parentheses ( )? Here’s the reason why:

>>> # List comprehension
>>> numbers = [x ** 2 for x in range(10)]
>>> type(numbers)
<class 'list'>
>>> numbers
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> # But this is _not_ a tuple comprehension:
>>> numbers = (x ** 2 for x in range(10))
>>> type(numbers)
<class 'generator'>
>>> numbers
<generator object <genexpr> at 0x7fdf4f853ba0>

>>> # You need to use `tuple(...)`:
>>> numbers = tuple(x ** 2 for x in range(10))
>>> type(numbers)
<class 'tuple'>
>>> numbers
(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)

In draw_stars(), you need to loop through both star_positions and star_sizes. Therefore, you use Python’s zip() function to ‘zip’ these two tuples together and loop through them simultaneously.

In the animation loop, you draw the stars before you draw the flower to make sure the stars are ‘behind’ the flower in the drawing.

The animation now has stars, the flower, and the sky all changing their nighttime colours into daytime ones.

Adding The Sun To The Scene

When you add the sun, there’s a new challenge. Setting the sun’s colour transformation should not be too difficult as this follows the same pattern as everything else. However, you’ll also need the sun to rise in the sky.

The sun’s x-coordinate is constant throughout the animation. However, the y-coordinate changes. As with colour, you also have a start and end value for the y-coordinate. Therefore, you can use the RangeLimits named tuple class for the y-coordinate range. The values within it are floats instead of Colour objects.

You also need to define calculate_movement_change() which performs a similar task to calculate_colour_change(). Its input arguments are the start and end y-coordinates and the number of steps in the animation:

# sunrise.py

import random
import turtle

from collections import namedtuple

number_of_steps = 500
number_of_stars = 200
sun_size = 150
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)
petal_colour = RangeLimits(
    Colour(50 / 255, 50 / 255, 50 / 255),
    Colour(138 / 255, 43 / 255, 226 / 255),
)
flower_centre_colour = RangeLimits(
    Colour(30 / 255, 30 / 255, 30 / 255),
    Colour(255 / 255, 165 / 255, 0 / 255),
)
stem_colour = RangeLimits(
    Colour(15 / 255, 15 / 255, 15 / 255),
    Colour(34 / 255, 139 / 255, 34 / 255),
)
star_colour = RangeLimits(
    Colour(1, 1, 1),
    sky_colour.end,
)
sun_colour = RangeLimits(
    Colour(10 / 255, 10 / 255, 10 / 255),
    Colour(249 / 255, 215 / 255, 28 / 255),
)

sun_x_coordinate = -width / 3
sun_y_position = RangeLimits(
    -height / 2 - sun_size / 2,
    height / 2 - height / 8,
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

def calculate_movement_change(start, end, n_steps):
    return (end - start) / n_steps

# Set up the window for the animation
# Sky
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")
sky.bgcolor(sky_colour.start)

# Flower and Stem
flower = turtle.Turtle()
flower.hideturtle()
stem = turtle.Turtle()
stem.hideturtle()
stem.right(90)
stem.pensize(10)

def draw_flower(petal_col, flower_centre_col, stem_col):
    stem.clear()
    stem.color(stem_col)
    stem.forward(height / 2)
    stem.forward(-height / 2)

    flower.clear()
    flower.color(petal_col)
    # Draw petals
    for _ in range(6):
        flower.forward(100)
        flower.dot(75)
        flower.forward(-100)
        flower.left(360 / 6)
    # Draw centre of flower
    flower.color(flower_centre_col)
    flower.dot(175)

# Draw the initial flower using the starting colours
draw_flower(
    petal_colour.start,
    flower_centre_colour.start,
    stem_colour.start,
)

# Stars
stars = turtle.Turtle()
stars.hideturtle()
stars.penup()

# Generate pairs of coordinates for the star positions
star_positions = tuple(
    (
        random.randint(-width // 2, width // 2),
        random.randint(-width // 2, width // 2),
    )
    for _ in range(number_of_stars)
)
# …and size for the stars
star_sizes = tuple(
    random.randint(2, 8) for _ in range(number_of_stars)
)

def draw_stars(colour):
    stars.clear()
    stars.color(colour)
    for position, size in zip(star_positions, star_sizes):
        stars.setposition(position)
        stars.dot(size)

# Draw the initial stars using the starting colour
draw_stars(star_colour.start)

# Sun
sun = turtle.Turtle()
sun.hideturtle()
sun.setposition(sun_x_coordinate, sun_y_position.start)

def draw_sun(sun_col, sun_height):
    sun.clear()
    sun.color(sun_col)
    sun.sety(sun_height)
    sun.dot(sun_size)

draw_sun(sun_colour.start, sun_y_position.start)

####
# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)
petal_colour_steps = calculate_colour_change(
    petal_colour.start, petal_colour.end, number_of_steps
)
flower_centre_colour_steps = calculate_colour_change(
    flower_centre_colour.start,
    flower_centre_colour.end,
    number_of_steps,
)
stem_colour_steps = calculate_colour_change(
    stem_colour.start, stem_colour.end, number_of_steps
)
star_colour_steps = calculate_colour_change(
    star_colour.start, star_colour.end, number_of_steps
)
sun_colour_steps = calculate_colour_change(
    sun_colour.start, sun_colour.end, number_of_steps
)

sun_movement_steps = calculate_movement_change(
    sun_y_position.start, sun_y_position.end, number_of_steps
)

####
# Start animation
current_sky_colour = sky_colour.start
current_petal_colour = petal_colour.start
current_flower_centre_colour = flower_centre_colour.start
current_stem_colour = stem_colour.start
current_star_colour = star_colour.start
current_sun_colour = sun_colour.start

current_sun_y_position = sun_y_position.start

for _ in range(number_of_steps):
    # Sky
    current_sky_colour = Colour(
        current_sky_colour.red + sky_colour_steps.red,
        current_sky_colour.green + sky_colour_steps.green,
        current_sky_colour.blue + sky_colour_steps.blue,
    )
    # Change the background to use the new colour
    sky.bgcolor(current_sky_colour)

    # Stars
    current_star_colour = Colour(
        current_star_colour.red + star_colour_steps.red,
        current_star_colour.green + star_colour_steps.green,
        current_star_colour.blue + star_colour_steps.blue,
    )

    draw_stars(current_star_colour)

    # Flower and Stem
    current_petal_colour = Colour(
        current_petal_colour.red + petal_colour_steps.red,
        current_petal_colour.green + petal_colour_steps.green,
        current_petal_colour.blue + petal_colour_steps.blue,
    )
    current_flower_centre_colour = Colour(
        current_flower_centre_colour.red
        + flower_centre_colour_steps.red,
        current_flower_centre_colour.green
        + flower_centre_colour_steps.green,
        current_flower_centre_colour.blue
        + flower_centre_colour_steps.blue,
    )
    current_stem_colour = Colour(
        current_stem_colour.red + stem_colour_steps.red,
        current_stem_colour.green + stem_colour_steps.green,
        current_stem_colour.blue + stem_colour_steps.blue,
    )

    # Draw the flower again with the new colours
    draw_flower(
        current_petal_colour,
        current_flower_centre_colour,
        current_stem_colour,
    )

    # Sun
    current_sun_colour = Colour(
        current_sun_colour.red + sun_colour_steps.red,
        current_sun_colour.green + sun_colour_steps.green,
        current_sun_colour.blue + sun_colour_steps.blue,
    )
    current_sun_y_position += sun_movement_steps

    draw_sun(current_sun_colour, current_sun_y_position)

    sky.update()

turtle.done()

The function draw_sun() needs the sun’s colour and y-position. You use Turtle‘s setposition() initially to set both the x- and y-positions of the sun. However, in draw_sun() you can use sety() since the x-coordinate no longer changes.

Incrementing current_sun_y_position in the animation loop is simpler than with the colours since the value is a single float rather than a named tuple.

The animation is now complete:

Should You Use Named Tuples In The Animation Loop?

Earlier in the article, we discussed how tuples are ideal for colours and other values that do not change. However, in the current version of the code, you’re using named tuples to store the colours during the animation loop, too. These are the colours named current_<...>_colour.

Because tuples are immutable objects, you have to create new Colour named tuples in the for loop and reassign them to the same variable names. Tuples are not ideal for this.

Instead, you can convert the named tuples to dictionaries before the for loop. Dictionaries are mutable types and more suited for values that need to change frequently.

You can refactor the code using named tuple’s _asdict() method which converts the named tuple to a dictionary:

# sunrise.py

import random
import turtle

from collections import namedtuple

number_of_steps = 500
number_of_stars = 200
sun_size = 150
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)
petal_colour = RangeLimits(
    Colour(50 / 255, 50 / 255, 50 / 255),
    Colour(138 / 255, 43 / 255, 226 / 255),
)
flower_centre_colour = RangeLimits(
    Colour(30 / 255, 30 / 255, 30 / 255),
    Colour(255 / 255, 165 / 255, 0 / 255),
)
stem_colour = RangeLimits(
    Colour(15 / 255, 15 / 255, 15 / 255),
    Colour(34 / 255, 139 / 255, 34 / 255),
)
star_colour = RangeLimits(
    Colour(1, 1, 1),
    sky_colour.end,
)
sun_colour = RangeLimits(
    Colour(10 / 255, 10 / 255, 10 / 255),
    Colour(249 / 255, 215 / 255, 28 / 255),
)

sun_x_coordinate = -width / 3
sun_y_position = RangeLimits(
    -height / 2 - sun_size / 2,
    height / 2 - height / 8,
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

def calculate_movement_change(start, end, n_steps):
    return (end - start) / n_steps

# Set up the window for the animation
# Sky
sky = turtle.Screen()
sky.setup(width, height)
sky.tracer(0)
sky.title("Good morning…")
sky.bgcolor(sky_colour.start)

# Flower and Stem
flower = turtle.Turtle()
flower.hideturtle()
stem = turtle.Turtle()
stem.hideturtle()
stem.right(90)
stem.pensize(10)

def draw_flower(petal_col, flower_centre_col, stem_col):
    stem.clear()
    stem.color(stem_col)
    stem.forward(height / 2)
    stem.forward(-height / 2)

    flower.clear()
    flower.color(petal_col)
    # Draw petals
    for _ in range(6):
        flower.forward(100)
        flower.dot(75)
        flower.forward(-100)
        flower.left(360 / 6)
    # Draw centre of flower
    flower.color(flower_centre_col)
    flower.dot(175)

# Draw the initial flower using the starting colours
draw_flower(
    petal_colour.start,
    flower_centre_colour.start,
    stem_colour.start,
)

# Stars
stars = turtle.Turtle()
stars.hideturtle()
stars.penup()

# Generate pairs of coordinates for the star positions
star_positions = tuple(
    (
        random.randint(-width // 2, width // 2),
        random.randint(-width // 2, width // 2),
    )
    for _ in range(number_of_stars)
)
# …and size for the stars
star_sizes = tuple(
    random.randint(2, 8) for _ in range(number_of_stars)
)

def draw_stars(colour):
    stars.clear()
    stars.color(colour)
    for position, size in zip(star_positions, star_sizes):
        stars.setposition(position)
        stars.dot(size)

# Draw the initial stars using the starting colour
draw_stars(star_colour.start)

# Sun
sun = turtle.Turtle()
sun.hideturtle()
sun.setposition(sun_x_coordinate, sun_y_position.start)

def draw_sun(sun_col, sun_height):
    sun.clear()
    sun.color(sun_col)
    sun.sety(sun_height)
    sun.dot(sun_size)

draw_sun(sun_colour.start, sun_y_position.start)

####
# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)
petal_colour_steps = calculate_colour_change(
    petal_colour.start, petal_colour.end, number_of_steps
)
flower_centre_colour_steps = calculate_colour_change(
    flower_centre_colour.start,
    flower_centre_colour.end,
    number_of_steps,
)
stem_colour_steps = calculate_colour_change(
    stem_colour.start, stem_colour.end, number_of_steps
)
star_colour_steps = calculate_colour_change(
    star_colour.start, star_colour.end, number_of_steps
)
sun_colour_steps = calculate_colour_change(
    sun_colour.start, sun_colour.end, number_of_steps
)

sun_movement_steps = calculate_movement_change(
    sun_y_position.start, sun_y_position.end, number_of_steps
)

####
# Start animation
current_sky_colour = sky_colour.start._asdict()
current_petal_colour = petal_colour.start._asdict()
current_flower_centre_colour = flower_centre_colour.start._asdict()
current_stem_colour = stem_colour.start._asdict()
current_star_colour = star_colour.start._asdict()
current_sun_colour = sun_colour.start._asdict()

current_sun_y_position = sun_y_position.start

for _ in range(number_of_steps):
    # Sky
    current_sky_colour["red"] += sky_colour_steps.red
    current_sky_colour["green"] += sky_colour_steps.green
    current_sky_colour["blue"] += sky_colour_steps.blue
    # Change the background to use the new colour
    sky.bgcolor(current_sky_colour.values())

    # Stars
    current_star_colour["red"] += star_colour_steps.red
    current_star_colour["green"] += star_colour_steps.green
    current_star_colour["blue"] + star_colour_steps.blue

    draw_stars(current_star_colour.values())

    # Flower and Stem
    current_petal_colour["red"] += petal_colour_steps.red
    current_petal_colour["green"] += petal_colour_steps.green
    current_petal_colour["blue"] += petal_colour_steps.blue

    current_flower_centre_colour["red"] += flower_centre_colour_steps.red
    current_flower_centre_colour["green"] += flower_centre_colour_steps.green
    current_flower_centre_colour["blue"] += flower_centre_colour_steps.blue

    current_stem_colour["red"] += stem_colour_steps.red
    current_stem_colour["green"] += stem_colour_steps.green
    current_stem_colour["blue"] += stem_colour_steps.blue

    # Draw the flower again with the new colours
    draw_flower(
        current_petal_colour.values(),
        current_flower_centre_colour.values(),
        current_stem_colour.values(),
    )

    # Sun
    current_sun_colour["red"] += sun_colour_steps.red
    current_sun_colour["green"] += sun_colour_steps.green
    current_sun_colour["blue"] += sun_colour_steps.blue
    current_sun_y_position += sun_movement_steps

    draw_sun(current_sun_colour.values(), current_sun_y_position)

    sky.update()

turtle.done()

There are changes in the for loop, too. You’re no longer creating new Colour named tuples. Instead, you’re changing the colour values within the dictionaries using the increment operator +=.

Then, you pass the values of the dictionary as arguments for sky.bgcolor(), draw_stars(), draw_flower(), and draw_sun(). You can use dictionary’s value() method to create an iterable that can be used in all those functions.

You won’t be able to notice any changes between the animation from this version and the one you completed earlier which didn’t use dictionaries.

So, Why Bother?

If the animation looks the same, why bother making this change?

In this project, it doesn’t matter. However, this is a good example to get us to think about which data types to use. When using named tuples in the for loop, the program must create several new named tuples in every frame. Creating new objects takes time.

But updating a dictionary is efficient. In the refactored version, you’re not creating new objects in the for loop but updating existing ones.

You can compare the two versions by timing them. However, most of the time is dedicated to displaying graphics on the screen in an animation such as this one.

You can compare the efficiency of the two versions by stripping out the drawing in the animation and just comparing the code that updates the colours.

Let’s use the timeit module to time the for loops in both versions of the code. Start with timing the original version which uses named tuples throughout, including in the animation for loop:

# sunrise_performance_version1.py

import timeit

setup_first_version = """
import random

from collections import namedtuple

number_of_steps = 500
number_of_stars = 200
sun_size = 150
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)
petal_colour = RangeLimits(
    Colour(50 / 255, 50 / 255, 50 / 255),
    Colour(138 / 255, 43 / 255, 226 / 255),
)
flower_centre_colour = RangeLimits(
    Colour(30 / 255, 30 / 255, 30 / 255),
    Colour(255 / 255, 165 / 255, 0 / 255),
)
stem_colour = RangeLimits(
    Colour(15 / 255, 15 / 255, 15 / 255),
    Colour(34 / 255, 139 / 255, 34 / 255),
)
star_colour = RangeLimits(
    Colour(1, 1, 1),
    sky_colour.end,
)
sun_colour = RangeLimits(
    Colour(10 / 255, 10 / 255, 10 / 255),
    Colour(249 / 255, 215 / 255, 28 / 255),
)

sun_x_coordinate = -width / 3
sun_y_position = RangeLimits(
    -height / 2 - sun_size / 2,
    height / 2 - height / 8,
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

def calculate_movement_change(start, end, n_steps):
    return (end - start) / n_steps

# Generate pairs of coordinates for the star positions
star_positions = tuple(
    (
        random.randint(-width // 2, width // 2),
        random.randint(-width // 2, width // 2),
    )
    for _ in range(number_of_stars)
)
# …and size for the stars
star_sizes = tuple(
    random.randint(2, 8) for _ in range(number_of_stars)
)

# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)
petal_colour_steps = calculate_colour_change(
    petal_colour.start, petal_colour.end, number_of_steps
)
flower_centre_colour_steps = calculate_colour_change(
    flower_centre_colour.start,
    flower_centre_colour.end,
    number_of_steps,
)
stem_colour_steps = calculate_colour_change(
    stem_colour.start, stem_colour.end, number_of_steps
)
star_colour_steps = calculate_colour_change(
    star_colour.start, star_colour.end, number_of_steps
)
sun_colour_steps = calculate_colour_change(
    sun_colour.start, sun_colour.end, number_of_steps
)

sun_movement_steps = calculate_movement_change(
    sun_y_position.start, sun_y_position.end, number_of_steps
)

####
# Start animation
current_sky_colour = sky_colour.start
current_petal_colour = petal_colour.start
current_flower_centre_colour = flower_centre_colour.start
current_stem_colour = stem_colour.start
current_star_colour = star_colour.start
current_sun_colour = sun_colour.start

current_sun_y_position = sun_y_position.start
"""


animation_loop_first_version = """
for _ in range(number_of_steps):
    # Sky
    current_sky_colour = Colour(
        current_sky_colour.red + sky_colour_steps.red,
        current_sky_colour.green + sky_colour_steps.green,
        current_sky_colour.blue + sky_colour_steps.blue,
    )

    # Stars
    current_star_colour = Colour(
        current_star_colour.red + star_colour_steps.red,
        current_star_colour.green + star_colour_steps.green,
        current_star_colour.blue + star_colour_steps.blue,
    )

    # Flower and Stem
    current_petal_colour = Colour(
        current_petal_colour.red + petal_colour_steps.red,
        current_petal_colour.green + petal_colour_steps.green,
        current_petal_colour.blue + petal_colour_steps.blue,
    )
    current_flower_centre_colour = Colour(
        current_flower_centre_colour.red
        + flower_centre_colour_steps.red,
        current_flower_centre_colour.green
        + flower_centre_colour_steps.green,
        current_flower_centre_colour.blue
        + flower_centre_colour_steps.blue,
    )
    current_stem_colour = Colour(
        current_stem_colour.red + stem_colour_steps.red,
        current_stem_colour.green + stem_colour_steps.green,
        current_stem_colour.blue + stem_colour_steps.blue,
    )

    # Sun
    current_sun_colour = Colour(
        current_sun_colour.red + sun_colour_steps.red,
        current_sun_colour.green + sun_colour_steps.green,
        current_sun_colour.blue + sun_colour_steps.blue,
    )
    current_sun_y_position += sun_movement_steps
"""

print(
    timeit.timeit(
        animation_loop_first_version,
        setup=setup_first_version,
        number=1_000,
    )
)

Start from the bottom of this script. You’re running timeit.timeit() with three arguments:

  • animation_loop_first_version is the code you want to time. The code is passed into timeit() as a string. You define this variable as a triple-quoted string just above the timeit() call. This includes the code in the animation for loop, excluding those lines responsible for drawing on the screen.
  • setup=setup_first_version includes the code you want to run before you start timing the main code. This is another triple-quoted string which includes the code before the animation for loop, excluding those lines responsible for drawing on the screen.
  • number=1_000 is the argument that sets how many times timeit() should run the code you want to time. Therefore, you’re timing 1,000 runs of the animation without displaying it.

When you run this script, you get the following output:

1.631227905

The code took around 1.6 seconds to run 1,000 times on my setup.

You can create a similar script for the second version where dictionaries replaced named tuples in the for loop:

# sunrise_performance_version2.py

import timeit

setup_first_version = """
import random

from collections import namedtuple

number_of_steps = 500
number_of_stars = 200
sun_size = 150
width = 1200
height = 800

# Create Named Tuple Classes
RangeLimits = namedtuple("RangeLimits", "start, end")
Colour = namedtuple("Colour", "red, green, blue")

sky_colour = RangeLimits(
    Colour(0 / 255, 0 / 255, 0 / 255),
    Colour(0 / 255, 191 / 255, 255 / 255),
)
petal_colour = RangeLimits(
    Colour(50 / 255, 50 / 255, 50 / 255),
    Colour(138 / 255, 43 / 255, 226 / 255),
)
flower_centre_colour = RangeLimits(
    Colour(30 / 255, 30 / 255, 30 / 255),
    Colour(255 / 255, 165 / 255, 0 / 255),
)
stem_colour = RangeLimits(
    Colour(15 / 255, 15 / 255, 15 / 255),
    Colour(34 / 255, 139 / 255, 34 / 255),
)
star_colour = RangeLimits(
    Colour(1, 1, 1),
    sky_colour.end,
)
sun_colour = RangeLimits(
    Colour(10 / 255, 10 / 255, 10 / 255),
    Colour(249 / 255, 215 / 255, 28 / 255),
)

sun_x_coordinate = -width / 3
sun_y_position = RangeLimits(
    -height / 2 - sun_size / 2,
    height / 2 - height / 8,
)

def calculate_colour_change(
    start: Colour,
    end: Colour,
    n_steps: int,
):
    red_step = (end.red - start.red) / n_steps
    green_step = (end.green - start.green) / n_steps
    blue_step = (end.blue - start.blue) / n_steps

    return Colour(red_step, green_step, blue_step)

def calculate_movement_change(start, end, n_steps):
    return (end - start) / n_steps

# Generate pairs of coordinates for the star positions
star_positions = tuple(
    (
        random.randint(-width // 2, width // 2),
        random.randint(-width // 2, width // 2),
    )
    for _ in range(number_of_stars)
)
# …and size for the stars
star_sizes = tuple(
    random.randint(2, 8) for _ in range(number_of_stars)
)

# Calculate step sizes needed for colour changes
sky_colour_steps = calculate_colour_change(
    sky_colour.start, sky_colour.end, number_of_steps
)
petal_colour_steps = calculate_colour_change(
    petal_colour.start, petal_colour.end, number_of_steps
)
flower_centre_colour_steps = calculate_colour_change(
    flower_centre_colour.start,
    flower_centre_colour.end,
    number_of_steps,
)
stem_colour_steps = calculate_colour_change(
    stem_colour.start, stem_colour.end, number_of_steps
)
star_colour_steps = calculate_colour_change(
    star_colour.start, star_colour.end, number_of_steps
)
sun_colour_steps = calculate_colour_change(
    sun_colour.start, sun_colour.end, number_of_steps
)

sun_movement_steps = calculate_movement_change(
    sun_y_position.start, sun_y_position.end, number_of_steps
)

####
# Start animation
current_sky_colour = sky_colour.start._asdict()
current_petal_colour = petal_colour.start._asdict()
current_flower_centre_colour = flower_centre_colour.start._asdict()
current_stem_colour = stem_colour.start._asdict()
current_star_colour = star_colour.start._asdict()
current_sun_colour = sun_colour.start._asdict()

current_sun_y_position = sun_y_position.start
"""

animation_loop_first_version = """
for _ in range(number_of_steps):
    # Sky
    current_sky_colour["red"] += sky_colour_steps.red
    current_sky_colour["green"] += sky_colour_steps.green
    current_sky_colour["blue"] += sky_colour_steps.blue

    # Stars
    current_star_colour["red"] += star_colour_steps.red
    current_star_colour["green"] += star_colour_steps.green
    current_star_colour["blue"] + star_colour_steps.blue

    # Flower and Stem
    current_petal_colour["red"] += petal_colour_steps.red
    current_petal_colour["green"] += petal_colour_steps.green
    current_petal_colour["blue"] += petal_colour_steps.blue

    current_flower_centre_colour["red"] += flower_centre_colour_steps.red
    current_flower_centre_colour["green"] += flower_centre_colour_steps.green
    current_flower_centre_colour["blue"] += flower_centre_colour_steps.blue

    current_stem_colour["red"] += stem_colour_steps.red
    current_stem_colour["green"] += stem_colour_steps.green
    current_stem_colour["blue"] += stem_colour_steps.blue

    # Sun
    current_sun_colour["red"] += sun_colour_steps.red
    current_sun_colour["green"] += sun_colour_steps.green
    current_sun_colour["blue"] += sun_colour_steps.blue
    current_sun_y_position += sun_movement_steps
"""

print(
    timeit.timeit(
        animation_loop_first_version,
        setup=setup_first_version,
        number=1_000,
    )
)

The output of this script is:

0.7887224199999999

The second version takes approximately half the time to run compared to the first. Creating new named tuples in each frame is time-consuming!

Notice that the part of the code that works out the changes of colour and sun position do not take up too much time in both versions. The timings you obtain from these scripts is for 1,000 runs of the animation without displaying it on screen.

However, when you run the full animation code once, the animation takes a few seconds to run. That’s because displaying graphics on the screen is the most time-consuming part of the code.

Still, in other programs you may write, performance may be an issue. So it’s always helpful to think about when you should use one data type over another one. There are many factors to consider when choosing the data structures for your program. Efficiency is an important one of them.

Final Words

In this article, you explored using Python named tuples to write a Python turtle animation of a sunrise scene.

You worked out the changes in colour and position needed in each animation frame for each item in your drawing. The result is that the colours and positions change smoothly as the animation runs.

Named tuples are ideal for defining the colours at the start and end of the animation. However, they’re not the best choice for storing the changing colours within the animation loop. Mutable data types are better suited when you need to update data often.


Get the latest blog updates

No spam promise. You’ll get an email when a new blog post is published


2 thoughts on “Sunrise: A Python Turtle Animation Using Named Tuples”

  1. Instead of dict as an alternative for a namedtuple, I think a dataclass is more appropriate here. With dataclasses you can just use attributes, instead of items.
    You could also consider a custom class, but that’s more work (and requires more explanation).

    1. That could work too, but dicts work very well here (and I doubt there’s much, if any, efficiency benefit from using data classes) and it means the article can focus on one non-basic data type

Leave a Reply