Python Turtle.Terminator even after using exitonclick()
Asked Answered
W

6

5

I have tried to make functions for turtle to make it extremely easy to draw shapes. The code looks like this:

import turtle as t

def square():
     tw = t.Screen()
     for i in range(4):
          t.forward(100)
          t.right(90)
     tw.exitonclick()
def triangle():
     tw = t.Screen()
     for i in range(3):
          t.forward(100)
          t.right(120)
     tw.exitonclick()
def star():
     tw = t.Screen()
     for i in range(5):
          t.forward(150)
          t.right(144)
     tw.exitonclick()

When I run this code in shell a Terminator error occurs:

>>> square()
>>> triangle()
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    triangle()
  File "C:\Users\Manop\Desktop\XENON\turtleg.py", line 11, in triangle
    t.forward(100)
  File "<string>", line 5, in forward
turtle.Terminator
>>> star()
>>> square()
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    square()
  File "C:\Users\Manop\Desktop\XENON\turtleg.py", line 5, in square
    t.forward(100)
  File "<string>", line 5, in forward
turtle.Terminator
>>> 

I can't understand what the problem is, because I even used exitonclick().

Wartburg answered 6/8, 2017 at 17:2 Comment(1)
Minimal reproduction of a Terminator error on Python 3.10.2: import turtle;turtle.exitonclick();turtle.done(). The error is raised when calling turtle methods after closing the window with done(), 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
W
5

Your turtle program is structured incorrectly. You needn't do:

tw = t.Screen()
...
tw.exitonclick()

in every function. Screen() only needs to be called once; exitonclick() should only ever be called once. Try this restructuring:

import turtle as t

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.penup()
t.goto(150, 150)
t.pendown()
square()

t.penup()
t.goto(-150, 150)
t.pendown()
triangle()

t.penup()
t.goto(150, -150)
t.pendown()
star()

screen = t.Screen()
screen.exitonclick()

If you want to execute the code interactively, that's fine too. Just drop everything after the function definitions, load it into Python interactively and do:

>>> star()

or whatever you want to run. You don't need the call to Screen() and the exitonclick() doesn't make sense when working interactively.

Washrag answered 6/8, 2017 at 19:27 Comment(3)
Doesn't work (I have copy-pasted and run the code as it is). Clicking anywhere on he turtle screen -- even many times -- is just ignored.Thomajan
@Apostolos, after drawing completes, clicking anywere on the screen exits the program cleanly (in Python3). You seem to be solving a different problem: being able to exit cleanly any time during the drawing process. I don't see anything in the OP's question that implies that desire. Though that is an interesting problem, if that's what he really wants.Washrag
Yes, only when all drawing is completed and not anytime until then. This is not OK, esp. in case the drawing takes long. Please try my code below to see that you can exit anytime.Thomajan
F
5

I had the same error while I was working on a school project. After some research on the turtle library I have found a variable called TurtleScreen._RUNNING, if this variable is set to True a turtle window opens, if not you get the turtle.Terminator error. Every time you close a turtle screen, TurtleScreen._RUNNING is automatically set to True, if you want to avoid that, you can simply write this line of code TurtleScreen._RUNNING = True (of course you need to import turtle before).

Fibro answered 6/6, 2018 at 16:47 Comment(1)
This method isn't in the docs. Best not to mess with implementation details. Instead of a workaround by flipping an undocumented internal flag that could change at any time and might have unexpected side effects, it seems better to use exitonclick as it was intended, as the last statement in the program that is the final exit. If OP is trying to pause until a click event occurs, there are better ways to do that.Reggiereggis
A
1

Let the method screen.exitonclick() be the last statement in your code without indenting it.

You use this method when your using a Python IDE such as Pycharm, Spyder etc.

I don't know if you have heard of the method screen.mainloop()

This method enables you see the output of your code when you run it in a Python IDE.

Without this method, your output would appear in a flash.

I rewrote your code and here's mine

from turtle import Turtle

t=Turtle()

def square():
    t.up()
    t.setpos(-50,-50)
    t.down()
    for i in range(4):
        t.forward(100)
        t.right(90)

def triangle():
    t.up()
    t.setpos(50,50)
    t.down()
    for i in range(3):
        t.forward(100)
        t.right(120)

def star():
    t.up()
    t.setpos(-200,100)
    t.down()
    for i in range(5):
        t.forward(150)
        t.right(144)

square()
triangle()
star()
t.screen.exitonclick()

Here's the output output of my program

You can also check this excellent guide in Python turtle

Aldaaldan answered 9/8, 2017 at 0:47 Comment(2)
Doesn't work (I have copy-pasted and run the code as it is). Clicking anywhere on he turtle screen -- even many times -- is just ignored.Thomajan
It works here anyway, thanks!!! (python 3.8 in IDLEAbnormality
T
1

When you interrupt the turtle drawing, it gets angry and produces "abnormal termination" error. Use a "running" flag to stop the process at any point:

from turtle import Turtle

t=Turtle()

def square():
    global running
    t.up()
    t.setpos(-50,-50)
    t.down()
    for i in range(4):
        if not running: break; # Check 'running' here
        t.forward(100)
        t.right(90)

def triangle():
    global running
    t.up()
    t.setpos(50,50)
    t.down()
    for i in range(3):
        if not running: break; # Check 'running' here
        t.forward(100)
        t.right(120)

def star():
    global running
    t.up()
    t.setpos(-200,100)
    t.down()
    for i in range(5):
        if not running: break; # Check 'running' here
        t.forward(150)
        t.right(144)

def stop(x,y): # x,y are dummy but they are requested
  global running
  running = False  # Disable running

t.screen.onclick(stop) # Set a function for 'running'

running = True  # Enable running

square()
triangle()
star()

I tested the above code. Termination was smooth at all times.

Thomajan answered 12/4, 2018 at 22:1 Comment(0)
R
1

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:

Reggiereggis answered 30/5 at 22:23 Comment(0)
P
0

First, I used function debugest_question_reproduction to analyze the problem phenomenon of the original code.

Then, I used function debugest_code_optimization to optimize the original problem code and tested it to achieve the expected result, as shown in the figure below.

enter image description here

Note: My code uses pdb mode to construct the running environment of the original problem. If you use it formally, please comment out the related codes of import pdb; pdb.set_trace()

def debugest_stackoverflow_45534458_11280199():
    def debugest_question_reproduction():
        import turtle as t
        import pdb
        
        pdb.set_trace()

        def square():
            tw = t.Screen()
            for i in range(4):
                t.forward(100)
                t.right(90)
            tw.exitonclick()
            
        def triangle():
            tw = t.Screen()
            for i in range(3):
                t.forward(100)
                t.right(120)
            tw.exitonclick()
            
        def star():
            tw = t.Screen()
            for i in range(5):
                t.forward(150)
                t.right(144)
            tw.exitonclick()
    
    def debugest_code_optimization():
        import turtle as t
        import importlib
        import pdb
        
        pdb.set_trace()
        
        def click_exit(x, y):
            t.bye()

        def square():
            for i in range(4):
                t.forward(100)
                t.right(90)
            t.onscreenclick(click_exit)
            t.done()
            importlib.reload(t)
            
        def triangle():
            for i in range(3):
                t.forward(100)
                t.right(120)
            t.onscreenclick(click_exit)            
            t.done()
            importlib.reload(t)
            
        def star():
            for i in range(5):
                t.forward(150)
                t.right(144)
            t.onscreenclick(click_exit)
            t.done()
            importlib.reload(t)
    
    def main():
        #debugest_question_reproduction()
        debugest_code_optimization()
    
    main()
    
    def docref():
        # Python Turtle.Terminator even after using exitonclick()
        # https://mcmap.net/q/1898479/-python-turtle-terminator-even-after-using-exitonclick
        pass
Plumber answered 9/6 at 1:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.