next(years)
An end-of-year post • Some reflections • And there's some Python stuff in this post, too—a spinning globe animation
I wasn't sure whether to write an end-of-year article for The Python Coding Stack. But this past year has been eventful for me, and the next one will start with a big bang, so I think it's OK to write this end-of-year retrospective and include a brief peak at 2024.
But I'll put in some Python stuff, too. Don't worry, I won't just waffle! Therefore, this post will have a different style and format compared to my usual articles. I even have a reader feedback request for you.
So here's what's in this article:
An overview of the article (I rarely include this!)
A reader survey on what style of code blocks you prefer–please participate
A brief overview of 2023 (Note: it says brief)
A brief overview of what's coming in 2024 (Note: it also says brief)
A mini
turtle
-y, maths-y Python animation tutorial to finish off the article and the year
Let's start. I'll keep it brief* so you can return to your end-of-year parties and over-eating.
*I wrote this before writing the article. I had to consider editing it out, but I kept it in. The tutorial part and the end-of-year part are both relatively short, separately!
Code Blocks: Which Do You Prefer?
Many of you may not even be aware of what platform I'm using for these articles. I use Substack. One of its drawbacks is that it doesn't support proper code blocks. This is, erm, not ideal for a Python coding publication. For this reason, I've been using a workaround which has its own drawbacks. But I don't want to bias you, so I won't say too much.
Here are the two options.
Option A: Image Snippets with Gist links
Here's some code. This is the code of the animation I'll discuss later in this post. This is the format I always use in my posts, including a link to a Gist for those who want to copy-paste the code
Option B: Substack's Native Code Blocks
Here's the same code using Substack's native solution:
import turtle
import math
import random
width = 1000
height = 1000
radius = 300
rotation_speed = 0.01
window = turtle.Screen()
window.tracer(0)
window.setup(width, height)
window.bgcolor(0.3, 0.3, 0.3)
n_dots = 800
dots = []
while len(dots) < n_dots:
x = random.randint(
-window.window_width() // 2,
window.window_width() // 2,
)
y = random.randint(
-window.window_height() // 2,
window.window_height() // 2,
)
# Discard points that fall outside the sphere
if x ** 2 + y ** 2 > radius ** 2:
continue
dot = turtle.Turtle()
dot.penup()
dot.shape("circle")
dot.setposition(x, y)
dot.direction = random.choice([-1, 1])
dot.color(0.9, 0.9, 0.9)
dot.turtlesize(0.15)
dot.radians()
# Convert 'small_radius' and 'theta' into data attributes
dot.small_radius = math.sqrt(radius ** 2 - y ** 2)
# Starting angle
dot.theta = math.asin(x / dot.small_radius)
# And don't forget to append the dot to the list
dots.append(dot)
while True:
# Move each dot
for dot in dots:
x = dot.small_radius * math.sin(dot.theta)
dot.setx(x)
# Increase angle by 0.01 radians each iteration, for now
dot.theta += dot.direction * rotation_speed
window.update()
Your Turn
Which one do you prefer?
2023
What a year this has been. And I'm not referring to what's been going on in the world (such as the rise of AI). It's been a busy year for me. I launched The Python Coding Stack in April, and we're fast approaching 2,000 subscribers. I wrote articles before, but never with the frequency and focus of the past 8+ months. One of the reasons I wanted to shift my writing to this new platform was to change my mindset from writing once in a while to writing regularly and with more intent.
This is the 45th article in this period. Articles ranged from long series, such as the Harry Potter-themed Object-Oriented Programming series and data structure categories series, to standalone tutorials. From time to time I wrote about one of my favourite tools for understanding Python programming–analogies. Here's the three-part Monty and The White Room Analogy, my 'most favouritest' analogy of all time, and one I use in all my teaching. And another topic that shows up occasionally in my writing is the turtle
module. I like taking this module to the limits, such as in A Touch of Randomness Makes The Magic Sparkle • A Python 'turtle' Animation. There's another turtle
project later in this article.
Away from The Stack, I spent much of the latter part of the year gearing up and getting ready for two major projects. I soft-launched both recently, but they're shifting gear in January (I used the gear metaphor twice there–sorry!) So, I'll write about them in the 2024 section.
And 2023 is also the year when I moved to using my standing desk 100% of the time in standing position. I never thought this was possible. But perhaps you don't care about this, so I'll move on.
next(years)
The title of this article is "next(years)
". If you've been following The Python Coding Stack long enough, you would have read the article about iterators and another about generators. The two topics are closely linked.
Here's how we could achieve this rather pointless code:
You're defining a function called get_years()
that accepts one argument. However, this function definition doesn't have a return
statement. Instead, it has a yield
statement. This makes the function a generator function.
Therefore, calling the function creates a generator, which I assigned to the name years
. A generator is an iterator. So, we can use the built-in next()
function to get the next item in the generator.
The generator created by get_years()
is an infinite generator since it contains an infinite while
loop.
Now, you need to somehow keep the generator on hold for another year so you can use it again next December!
2024
I've been creating lots of Python content* in the past few years, especially this year. But with the turn of the year, I'm shifting to doing this full-time. And I was foolish enough to decide to launch two separate platforms at the same time.
*From time to time, some of you may have seen me post that I don't like the term "content creator", but I'll use it anyway.
The Python Coding Place
The Python Coding Place is the place for all my resources for professionals or hobbyists who want to learn to code in Python. The first video course for beginners is already available on the platform. But the central part of this platform launches on the 15th of January 2024 and will include lots more video courses, but also cohort-based courses at different levels and workshops and a forum and…well, lots more. It's the place for learning to code in Python (ha!)
There are already 50 members of The Place, even before the platform is formally launched. And there's still a significant discount for anyone who wants to join before launch. As it says on the tin, the pre-launch offer won't be there after launch! There's only one type of membership–a one-time fee that gives members indefinite access to everything. Yes, including the live workshops and cohort courses. These are included in the membership and will only be available for members.
Advert over.
Codetoday Unlimited
Many of you may not know that my primary activity during the working week for many years revolved around teaching Python coding to children and teenagers. I've been teaching kids since 2016. In all these years, I focused on live classes, first in person and later held remotely on Zoom. Over the years, I crafted and perfected a curriculum ideally suited to teaching coding in Python to younger people. Children and teenagers have different motivations and needs than adults learning to code.
And earlier today, I pushed the button to launch Codetoday Unlimited. This brings my curriculum and project-based learning approach to all teenagers worldwide. Codetoday Unlimited is a video course platform for self-led young learners. I've been recording lessons nearly full-time over the past weeks to pass on my perspective of Python coding, communicated in my own style. But there's also a forum where students can ask me questions and get feedback.
Young and, er, not so young
The Python Coding Place and Codetoday Unlimited are similar platforms. But the content is very different. I'm in a rather unusual position where, for almost a decade, I taught Python coding to both kids and adults. There are similarities, yes, but the teaching style and projects are different. So, one platform for both audiences was never an option for a multitude of reasons.
But this means that from January, I'll be able to assist anyone, young or less young (!), who wants to learn to code in Python, either through The Python Coding Place for the more mature learners or Codetoday Unlimited for under-18s.
And I even want to revise The Python Coding Book and make it available in other formats in 2024.
The 'Dots On The Globe' Animation
And finally, let's look at this animation:
The dots appear to be on the surface of a rotating sphere. However, the dots are only moving horizontally.
I saw a similar animation somewhere earlier this year, but I couldn't remember where. So I decided to write my own. And now, I'll also share this code with you as my final tutorial of the year.
I'll keep this brief, so this step-by-step tutorial won't be as detailed as my usual longer posts. I'll use the turtle
module for the animation.
One Dot On The Equator
Let's start with a simple problem to solve. Let's create one dot and place it on the equator. We'll need some maths to solve this problem.
First, you can set up the screen for the animation and create a single dot at the centre of the screen:
import turtle
width = 1000
height = 1000
radius = 300
window = turtle.Screen()
window.setup(width, height)
window.bgcolor(0.3, 0.3, 0.3)
x = y = 0
dot = turtle.Turtle()
dot.penup()
dot.shape("circle")
dot.setposition(x, y)
dot.color(0.9, 0.9, 0.9)
dot.turtlesize(0.15)
turtle.done()
The width
and height
parameters set the size of the window. And radius
refers to the radius of the imaginary sphere whose centre is at the centre of our window.
Setting x
and y
to 0
is unnecessary at this stage since Turtle
objects are created in the middle of the screen, which has coordinates (0, 0)
. But as you can imagine, we'll need different starting values for the dots later.
You also change the screen to a medium-dark grey and the dot to an off-white colour. The .turtlesize()
method scales the image of the Turtle
object. Therefore, the dot is 15% of its default size in this example. You can customise these values to suit your preferences, of course.
Let's make the dot move horizontally to the edge of the sphere. We could use the .forward()
method, but I'll take a longer route, which will serve us well later:
import turtle
width = 1000
height = 1000
radius = 300
window = turtle.Screen()
window.setup(width, height)
window.bgcolor(0.3, 0.3, 0.3)
x = y = 0
dot = turtle.Turtle()
dot.penup()
dot.shape("circle")
dot.setposition(x, y)
dot.color(0.9, 0.9, 0.9)
dot.turtlesize(0.15)
while True:
dot.setx(x)
# For now, let's try this…
x += 1
if x > radius:
print("The dot should turn around now…")
break
turtle.done()
The dot moves to the right until its x-coordinate is larger than the radius. For the time being, you stop the while
loop and print a note when the dot reaches this point. Here’s the animation so far:
But this is not what's required in our animation. We want the dot to turn around and start moving in the other direction.
Adjusting The Dot's Horizontal Speed
Let's bring in some maths (or 'math' for my readers who use American English!) We're looking at an imaginary sphere. The dot is fixed to the surface of the sphere as the sphere rotates, but we see the dot move horizontally in the 2-dimensional plane we observe. If the sphere is rotating at a constant speed, then the dot shouldn't move horizontally at a constant speed. The angular rotation is constant.
I'll assume the plane we observe is the xy-plane since this matches the coordinate system in the turtle
module, and the z-axis is sticking out from the plane of our screen.
Let's introduce 𝛳, the angle between the z-axis and the line joining the dot to the centre of the sphere. This angle is 0º when x=0
since the line joining the dot to the centre of the sphere is on the z-axis.
I could have included diagrams here, but I don't want to turn this article into a maths tutorial. Also, it's the end of the year, and I don't fancy creating diagrams right now!
It's this angle 𝛳 that changes at a constant rate when the sphere rotates. And we can work out the projection on the x-axis using trigonometry (whoever said the stuff we learn at school is not useful!)
import turtle
import math
width = 1000
height = 1000
radius = 300
window = turtle.Screen()
window.setup(width, height)
window.bgcolor(0.3, 0.3, 0.3)
x = y = 0
dot = turtle.Turtle()
dot.penup()
dot.shape("circle")
dot.setposition(x, y)
dot.color(0.9, 0.9, 0.9)
dot.turtlesize(0.15)
dot.radians()
theta = 0 # radians
while True:
x = radius * math.sin(theta)
dot.setx(x)
# Increase angle by 0.01 radians each iteration, for now
theta += 0.01
Note that I'm now using radians rather than degrees to represent angles. This is the preferred unit for angles in these situations. The turtle
module has the .radians()
method to switch from degrees to radians.
Instead of increasing the value of x
in each iteration of the while
loop, we now increase the value of the angle theta
and work out x
using trigonometry. We're using the math
module from the standard library. Let's see what the animation looks like now:
There are two key changes:
The dot is not moving at a constant horizontal speed. It slows down as it reaches the edge and speeds up towards the middle.
The dot changes direction when it reaches the edge of the sphere. It does so at both ends.
Recall that 360º is the same as 0º (or 2𝜋 is the same as 0 if using radians). So, constantly increasing the angle is the same as going around the circumference of the sphere.
This solves the problem of moving a dot around the sphere's equator. In the next section, we'll look at dots that aren't on the equator. We also haven't dealt with the situation when the dot doesn't start at x=0
, but we'll sort this out in the next section.
One Dot On A Small Circle of The Sphere
If you look back at the original animation, all dots are moving horizontally, but they're not all on the equator. Dots can be on any small circle of the sphere. In the context of the Earth, these would be the lines of latitude, but I'll use the more general term small circles to refer to these.
Whereas the radius of the equator is the same as the radius of the sphere, the radii of small circles are smaller than the sphere's radius. Geometry comes to the rescue again:
import turtle
import math
width = 1000
height = 1000
radius = 300
window = turtle.Screen()
window.setup(width, height)
window.bgcolor(0.3, 0.3, 0.3)
x = y = 0
dot = turtle.Turtle()
dot.penup()
dot.shape("circle")
dot.setposition(x, y)
dot.color(0.9, 0.9, 0.9)
dot.turtlesize(0.15)
dot.radians()
small_radius = math.sqrt(radius ** 2 - y ** 2)
# Starting angle
theta = math.asin(x / small_radius)
while True:
x = small_radius * math.sin(theta)
dot.setx(x)
# Increase angle by 0.01 radians each iteration, for now
theta += 0.01
You calculate the small radius using Pythagoras' theorem (which is useful in the real world, after all!) Next, you can work out the angle for any starting position of the dot in the xy-plane. There are some caveats which I'll get to soon.
You also replace radius
with small_radius
in the while
loop when working out the x
-coordinate of the dot. The dot is now going around a small circle of the sphere. This still applies to the equator, which is now a special case. The code above still has x
and y
as 0
. Therefore, the dot still goes around the equator. You can try changing the value and set x = y = 200
, say. You'll see that the dot is now in the top half of the imaginary sphere.
However, you can't use any number you want. If the sphere's radius is 300
, then the largest value you can use for x
and y
is about 212
. (It's 300/\sqrt(2) from our friend Pythagoras again.) The dot must be within the region of our window containing the sphere. You can see the error message you get if you try to go out of this region. Try using an integer greater than 212
for x
and y
.
But x
and y
don't have to be equal. Let's get a dot close to the top of the sphere by using x = 0
and y = 295
. Here's the animation now:
Therefore, as long as the dot is within the region of the window containing the sphere, you can show it revolving with the sphere.
Many Dots
It's time to create lots of dots. I'll go for 800
dots in this animation. You can randomly choose their x- and y-positions as long as they're on the sphere. One way of achieving this is to choose random values for x
and y
within a while
loop and discard any values that fall outside the sphere. The loop keeps repeating until you have the required number of dots.
You can store all the Turtle
objects representing the dots in a list.
However, it's not only the starting coordinates that are different. Each dot is associated with a different angle 𝛳 and a different small radius. You can assign different values to each Turtle
object by creating data attributes for the angle 𝛳 and the small radius. You can refer back to the Harry Potter OOP series to brush up on data attributes. Another term often used for data attributes is instance variables. I quite like the term instance variables, especially in this context, as it shows that we're creating variables specific to each instance of the Turtle
object. However, Python prefers to call these data attributes:
import turtle
import math
import random
width = 1000
height = 1000
radius = 300
rotation_speed = 0.01
window = turtle.Screen()
window.tracer(0)
window.setup(width, height)
window.bgcolor(0.3, 0.3, 0.3)
n_dots = 800
dots = []
while len(dots) < n_dots:
x = random.randint(
-window.window_width() // 2,
window.window_width() // 2,
)
y = random.randint(
-window.window_height() // 2,
window.window_height() // 2,
)
# Discard points that fall outside the sphere
if x ** 2 + y ** 2 > radius ** 2:
continue
dot = turtle.Turtle()
dot.penup()
dot.shape("circle")
dot.setposition(x, y)
dot.color(0.9, 0.9, 0.9)
dot.turtlesize(0.15)
dot.radians()
# Convert 'small_radius' and 'theta' into data attributes
dot.small_radius = math.sqrt(radius ** 2 - y ** 2)
# Starting angle
dot.theta = math.asin(x / dot.small_radius)
# And don't forget to append the dot to the list
dots.append(dot)
while True:
# Move each dot
for dot in dots:
x = dot.small_radius * math.sin(dot.theta)
dot.setx(x)
dot.theta += rotation_speed
window.update()
Here are the changes in this version:
You set
window.tracer(0)
when creating the screen object, and there'swindow.update()
in thewhile
loop to update the screen only at the end of each frame of the animation.The code to create the dot is now in a
while
loop. You choose random coordinates inside the window for the dot's starting position, but they're discarded if they fall outside the permitted region. Recall thatrandom.randint()
needs integer arguments. This is why you use the floor division operator//
.You also convert the variables
small_radius
andtheta
into data attributes (instance variables)dot.small_radius
anddot.theta
.And all the dots are collected in a list. You iterate through this list in the
while
loop to move each dot in each frame of the animation. Don't forget to also refactor thewhile
loop to use the new data attributes.
Here's the animation now:
This works. But since all the dots are moving in the same direction, they're all in one hemisphere of the sphere. You can stop here if you prefer. But if you want to scatter the dots across the whole sphere, you can assign random starting directions to the dots when you create them:
import turtle
import math
import random
width = 1000
height = 1000
radius = 300
rotation_speed = 0.01
window = turtle.Screen()
window.tracer(0)
window.setup(width, height)
window.bgcolor(0.3, 0.3, 0.3)
n_dots = 800
dots = []
while len(dots) < n_dots:
x = random.randint(
-window.window_width() // 2,
window.window_width() // 2,
)
y = random.randint(
-window.window_height() // 2,
window.window_height() // 2,
)
# Discard points that fall outside the sphere
if x ** 2 + y ** 2 > radius ** 2:
continue
dot = turtle.Turtle()
dot.penup()
dot.shape("circle")
dot.setposition(x, y)
dot.direction = random.choice([-1, 1])
dot.color(0.9, 0.9, 0.9)
dot.turtlesize(0.15)
dot.radians()
# Convert 'small_radius' and 'theta' into data attributes
dot.small_radius = math.sqrt(radius ** 2 - y ** 2)
# Starting angle
dot.theta = math.asin(x / dot.small_radius)
# And don't forget to append the dot to the list
dots.append(dot)
while True:
# Move each dot
for dot in dots:
x = dot.small_radius * math.sin(dot.theta)
dot.setx(x)
dot.theta += dot.direction * rotation_speed
window.update()
You create another data attribute called dot.direction
, which is either 1
or -1
. This value is used when working out the change in angle in the while
loop.
And this is the final animation:
The years
generator I created earlier will never end. It will keep yielding values forever. But my articles for 2023 have now come to an end. See you in 2024.
And don't forget to vote in the poll about code blocks above. As you can see, I used a mixture of styles in this article.
Happy New Year!
Code in this article uses Python 3.12
Stop Stack
#45
I'll keep this week's Stop Stack brief since I normally use it to update you on what's going on, but the main article took care of this today!
Recently published articles on The Python Coding Stack:
Do Not Try This At Home A bit of silliness for the holiday season • But please, don't code like this. Please • Plus some out-of-the-norm commentary • There's nothing ordinary about today's article
The Key To The 'key' Parameter in Python A parameter named
key
is present in several Python functions, such assorted()
. Let's explore what it is and how to use it.What's All the Fuss About 'lambda' Functions in Python? Python's
lambda
functions are seemingly obscure, until they aren't. They're almost mystical, until unveiled. Let's shed some light to dispel the obscurity and lift the mystique.In Conversation: Pawel and Stephen Discuss Matplotlib's New-ish subplot_mosaic() In recent years, Matplotlib introduced a new function for plotting several plots in one figure. We had a chat about
subplot_mosaic()
In Conversation: Rodrigo and Stephen Discuss Analogies When Learning To Code A conversation between Rodrigo Girão Serrão and Stephen Gruppetta on analogies in programming
Recently published articles on Breaking the Rules, my other substack about narrative technical writing:
The South Park Technical Writing Manual (Ep. 14) What can we learn from South Park? Yes, the satirical TV show
I Haven't Been Abducted by Aliens (Ep. --) Why this long lull since the last Breaking The Rules post?
The Selfish Reason (Ep. 13) Another reason for authors to innovate • Enjoying the writing process
The Consequential Detail (Ep. 12). Can a single letter or one blank line make a difference? (Spoiler Alert: Yes)
The Unexpected Audience (Ep. 11). What I'm learning from listening to Feynman's physics lectures
Stats on the Stack
Age: 8 months, 2 weeks, and 5 days old
Number of articles: 45
Total subscribers: 1,704
On the Paid tier: 70
Each article is the result of years of experience and many hours of work. Hope you enjoy each one and find them useful. If you're in a position to do so, you can support this Substack further with a paid subscription. In addition to supporting this work, you'll get access to the full archive of articles and some paid-only articles. Alternatively, if you become a member of The Python Coding Place, you'll get access to all articles on The Stack as part of that membership.
Spinning globe animation - very nice!
Instead of random dots what about the following:
cities = [
{"name": "Tokyo", "coordinates": (35.6895, 139.6917)},
{"name": "Delhi", "coordinates": (28.6139, 77.2090)},
{"name": "Shanghai", "coordinates": (31.
{"name": "Lahore", "coordinates": (31.5497, 74.3436)},
{"name": "London", "coordinates": (51.5074, -0.1278)},
{"name": "Lima", "coordinates": (-12.0464, -77.0428)},
{"name": "Bangkok", "coordinates": (13.7563, 100.5018)},
]
I am a beginner and not fit to adapt the code. But I can provide a longer list of cities.
Regards
Rolf
I voted for the Substack version only because the text is larger