Tap, Tap, Tap on The Tiny `turtle` Typewriter
A mini-post using `functools.partial()` and `lambda` functions to hack keybindings in Python's `turtle` module
What do keybindings in the turtle
module, the partial()
function in functools
, lambda
functions, and typewriters have in common?
Answer: This mini-post, of course. Put them all in a bowl, add seasoning, mix well, and you'll get this Tiny Turtle Typewriter:
Let's get started.
This is a mini-post. You guessed it. It's relatively short and concise. So, straight to the point.
Don't worry if you haven't used the turtle
module much. You'll do just fine.
The problem
You can bind keys to functions using the onkeypress()
method in the turtle
module. However, there's a limitation. The information about which key is pressed is not passed to the function called. Often, this is not an issue when writing the kind of programs this module is often used for. But if you want to push what you can achieve from this module to the limit, as I like to do often, you may need a hack to bypass this problem.
The typewriter
You'll encounter this problem if you try to write a typewriter program using turtle
. You could write a function for each key you want to include in your typewriter and then bind that function to its corresponding key. But life's too short for that. There must be a better way.
I'll give you two…
The keybinding
Let's start writing the code. At first, your typewrites will only be able to type a lowercase "a":
You create a screen and a turtle. Then you define the function type_letter()
. This function writes the letter "a" on the screen and moves the turtle forwards. You bind this function with the key representing lowercase "a" using window.onkeypress()
and window.listen()
. These are methods in the turtle
module. The listen()
method sets the focus on the screen to collect key press events. Now, you can type lots of "a"s:
Who fancies repeating this for all lowercase letters, then all uppercase letters followed by digits and all punctuation marks? No one? I thought so…
The solution (Ⅰ) • Using functools.partial()
The function you pass as the first argument to window.onkeypress()
cannot take any arguments. The string "a"
you pass as the second argument is used to call the function type_letter()
, but it's not passed to the function.
The function you bind to a key cannot take any arguments.
You need to pass an argument to this function to avoid the need to create lots of similar copies.
One way to satisfy both conditions is to use functools.partial()
. I wrote an article a few weeks ago discussing this method. If you're new to functools.partial()
or need a refresher, you can read Python's functools.partial() Lets You Pre-Fill A Function.
Let's update the code using functools.partial()
:
The function type_letter()
now has a parameter character
. This means it can no longer be used as an argument for window.onkeypress()
since only functions that don't take any arguments can be used.
However, instead of passing type_letter
directly to window.onkeypress()
, you pass the object returned by functools.partial()
. This is a "pre-filled" function that's equivalent to type_letter("a")
. The object returned by functools.partial()
can be used wherever you would normally use the function itself. But since it's "pre-filled" with an argument, you don't need to pass any arguments to it.
This code replicates the basic typewriter you had earlier, which is only useful to type a lowercase "a".
Extending to other keys
However, you can now repeat the keybinding for all the keys you want on your typewriter. Let's start with all the letters:
You can bind all the lowercase and uppercase letters by looping through string.ascii_letters
and binding each letter to a partial
object. These partial
objects are created by pre-filling type_letter()
with each character.
The for
loop creates several keybindings—one for each uppercase and lowercase letter.
Now, you can type more letters, not just "a":
And now you can add digits and punctuation marks. You can create a set called typewriter_keys
to contain all the keys you want to include. I'll start adding some comments in the code, too:
The set typewriter_keys
includes the concatenated string of letters, digits, and punctuation marks. You use the unpacking operator *
to separate these strings into separate characters.
On my system, I need to replace the minus sign "-"
with the string "minus"
. This may vary from system to system. You can try leaving "-"
in the set at first, but if this maps the "-"
character to other keys as well as the minus key, then you can replace it with the string "minus"
. The function type_letter()
also needs an if
statement to replace "minus"
back with "-"
in this case.
You remove items from a set by using the set difference operator -
, and you add items using the union operator |
.
Now, you can type all letters, digits, and punctuation marks. Incidentally, if you're a fast typer, you'll notice glitches as you're pressing keys faster than the turtle can write these on the screen. You can fix this by setting the tracer to 0
in the turtle
module, which stops the turtle from drawing every step when it moves, and then using window.update()
when you need to refresh the screen:
Starting from the top
Before you add spaces, backspaces, and newlines, you can tidy up the code a bit by moving the turtle cursor to the top-left of the screen before you start:
The writing will now start from the top left, and the turtle will no longer draw a line when moving. You also use character_width
as the font size in write()
and as the amount you want to move the turtle forward after you type each letter. You can adjust these values depending on your preference.
Adding spaces, backspaces, and newlines
Finally, you can add the space and newline characters by adding the strings "space"
and "Return"
to the set typewriter_keys
and then replacing the strings in type_letter()
:
In type_letter()
, the string "space"
is swapped for an actual space, and the string representing the keyboard's return key is replaced with a new line by moving the turtle to the start of the following line. There's a return
in this elif
block to prevent the turtle from writing the text "Return"! Note that the word return appears in two contexts in this code. The string "Return"
represents the keyboard's return or enter key, whereas return
is the standard Python keyword to return a value from a function and terminate the function.
You can now add spaces and newlines when you type.
Finally, you can add the backspace. I'll show the code in full in this section. There are four additions from the previous segment:
You added the string
"BackSpace"
to the settypewriter_keys
.You added a new
elif
block totype_letter
. The code in thiselif
block repeatstypwriter.undo()
twice to undo the last two actions performed by the turtle. By undoing the last two actions, you remove the last letter and the turtle's movement to the new letter position.You changed the turtle shape to a square and reshaped it into a flat rectangle using
shapesize()
to represent a cursor.You added a title to the window.
And there's one bonus change, too—dark mode!
Here is the code in full:
And this gives you the typewriter program:
The solution (Ⅱ) • Using a lambda
function
There's another option. You can use a lambda
function with a default value instead of using functools.partial()
to achieve the same result. Recall that you need to pass a function without any arguments to window.onkeypress()
, but you must also pass an argument to type_letter()
. Here's how you can replace the call to functools.partial()
with a lambda
function in window.onkeypress()
:
The lambda
function takes an input argument character
which has a default value. Therefore, the lambda
function is a function that doesn't need an argument, as required by window.onkeypress()
. However, the key pressed is still passed to type_letter()
by using the default value.
This code gives the same typewriter program as the previous version.
Final Words
And there you have it. The Tiny Turtle Typewriter program, which uses either functools.partial()
or a lambda
function to enable you to pass the key pressed to the callback function.
Code in this article uses Python 3.11
Stop Stack
#24
Recently published articles on The Python Coding Stack:
Python Quirks? Party Tricks? Peculiarities Revealed… (Paid article) Three "weird" Python behaviours that aren't weird at all
The Mayor of Py Town's Local Experiment: A Global Disaster. Why variables within functions are local
Time for Something Special • Special Methods in Python Classes (Harry Potter OOP Series #6) Year 6 at Hogwarts School of Codecraft and Algorithmancy • Special Methods (aka Dunder Methods)
A Picture is Worth More Than a Thousand Words • Images & 2D Fourier Transforms in Python. For article #20 on The Python Coding Stack, I'm revisiting one of my tutorials from the pre-Substack era
A One-Way Stream of Data • Iterators in Python (Data Structure Categories #6) Iterators • Part 6 of the Data Structure Categories Series
Recently published articles on Breaking the Rules, my other substack about narrative technical writing:
The Wrong Picture (Ep. 6). How I messed up • When an analogy doesn't work
The Broom and the Door Frame (Ep. 5). How the brain deals with stories
Mystery in the Manor (Ep. 4). The best story is the one narrated by the technical detail itself
Frame It (Ep. 3). You can't replace the technical content with a story. But you can frame it within a story.
Whizzing Through Wormholes (Ep. 2). Travelling to the other end of the universe—with the help of analogies
Stats on the Stack
Age: 4 months and 3 days old
Number of articles: 24
Subscribers: 854
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.
nice article!
How did you made the image at the end? is it from an human artist or AI generated?