If you're simply running turtle interactively, as appears to be the case for OP, then you can skip all mainloop
/exitonclick
/done
calls, and most of the below doesn't apply.
As it's currently designed (CPython 3.12), turtle only gives you one screen (window). When this screen is closed, turtle considers itself terminated and will raise a Terminator
error if you try to do anything further with it. exitonclick
or mainloop
need to be the last statement in a turtle program.
Taking a step back, most windowed applications launch once and exit completely when closed. Having an application that reopens another window after the user clicks the "X" to close it strikes me as a potentially surprising and spammy user experience. As such, the fact that turtle doesn't (currently) support multiple windows doesn't seem like a serious oversight.
If your actual goal is to use one screen and let the user click the screen to move on to the next shape, use onscreenclick
rather than exitonclick
. Here's a direct single-screen translation of your logic:
from turtle import Screen, Turtle
def next_shape(*_):
if not shapes:
return Screen().bye()
Screen().reset()
Screen().onclick(None)
shapes.pop(0)()
Screen().onclick(next_shape)
def square():
for i in range(4):
t.forward(100)
t.right(90)
def triangle():
for i in range(3):
t.forward(100)
t.right(120)
def star():
for i in range(5):
t.forward(150)
t.right(144)
t = Turtle()
shapes = [square, triangle, star]
Screen().onclick(next_shape)
Screen().mainloop()
In the above code, shapes
lists the "steps" we want to allow the user to click through, and next_shape()
steps through the states one by one. Screen().reset()
is used to clear the state between each drawing.
If the next_shape
abstraction is confusing, you can always plop the reset
and onclick
calls directly into each drawing function. This is a bit repetitive and tightly couples user interaction and drawing, but might be more accessible for beginners.
If you really want to use multiple windows, keep in mind that messing with internal state (setting turtle.TurtleScreen._RUNNING = True
in this case) can appear to work at first, but things can fail randomly if turtle relies on the state for keeping itself consistent. You're basically in an "all bets are off" situation, and the program can crash for an arbitrary reason, either in the current version, or in a future version.
Turtle is generally used in education settings and has a stable API, so there's no major risk here, but it's worth mentioning and avoiding if possible. For educators, don't let your students get in the habit of relying on undocumented internals, hacks and workarounds. The docs and public API should be the only source of truth.
An alternative to the ._RUNNING = True
hack is to use subprocesses. The downside is that sharing state between programs is more difficult since each program is its own self-contained process. Here's an example.
main.py:
import sys
from subprocess import run
run([sys.executable, "square.py"])
run([sys.executable, "triangle.py"])
run([sys.executable, "star.py"])
square.py:
from turtle import Screen, Turtle
def square():
for i in range(4):
t.forward(100)
t.right(90)
t = Turtle()
square()
Screen().exitonclick()
triangle.py:
from turtle import Screen, Turtle
def triangle():
for i in range(3):
t.forward(100)
t.right(120)
t = Turtle()
triangle()
Screen().exitonclick()
star.py:
from turtle import Screen, Turtle
def star():
for i in range(5):
t.forward(150)
t.right(144)
t = Turtle()
star()
Screen().exitonclick()
The above can be condensed into a single file by using a command line argument to determine which shape to draw:
import subprocess
import sys
from turtle import Screen, Turtle
def square(t):
for i in range(4):
t.forward(100)
t.right(90)
def triangle(t):
for i in range(3):
t.forward(100)
t.right(120)
def star(t):
for i in range(5):
t.forward(150)
t.right(144)
shapes = "square", "triangle", "star"
if len(sys.argv) == 1:
for shape in shapes:
subprocess.run([sys.executable, __file__, shape])
else:
if sys.argv[1] in shapes:
globals()[sys.argv[1]](Turtle())
Screen().exitonclick()
else:
print(f"Invalid function name '{sys.argv[1]}'", file=sys.stderr)
exit(1)
You can also spread this to two files: one for "driving" the turtle programs and managing subprocesses, and another for doing the actual drawing, using argv[1]
passed in from the parent process to determine which drawing to do.
See these other similar examples of managing multiple states in one turtle screen session:
import turtle;turtle.exitonclick();turtle.done()
. The error is raised when calling turtle methods after closing the window withdone()
,bye()
,exitonclick()
,mainloop()
, etc. Typically, only use exactly one of these calls at the very end of the application, then stop using turtle afterwards. – Reggiereggis