gh-65276: Fixed Turtle library circle method bug#104022
Open
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Behavior:
Having used multiple turtle instances to create various object for an interactive Tic-Tac-Toe game, a user noticed that setting turtle speeds to 0 (instant) lead to "disappearing" objects. The bug was with the
circlemethod, which was called on click. The user had created tiles and lines to display the "board" using multiple turtle instances, and when thecirclemethod was called for speed 0 turtles, the board would display on top of all lines and circle drawn, effectively making them disappear.The issue:
When setting speed to 0 in particular, the
circlemethod would call the_tracermethod to setself._tracingandself._delayvalueto zero, before drawing the circle, and then resetting both of these values via the_tracermethod. The issue is in the final call to_tracerthat resets the two values. This is because_traceractually callsscreen.tracer, and when resetting with a nonzero first argument,screen.traceractually callsscreen.update()on line 1272. Thisupdatemethod redraws EVERY turtle object that shares the current screen, which is both unnecessary in this case, and also creates the actual bug itself.Eventually, the
screen.update()method callst._drawturtle()for every turtle, which can (and does in this case) include background turtle objects like the user's board objects. Despite it actually iterating through the turtle objects in the right order, thet._drawturtle()method tries to draw the first object on top. This takes place in multiplescreen._drawpolycalls with atop=Trueargument. One might thinktop=Truewould behave like a stack, i.e., the most recent call like this would actually be on top. In reality, it either behaves like a queue, or just negates every call except the first. In the case of the user's code, the first-created turtle object (the board) got thescreen._drawpoly(..., top=True)call first, and thus, displayed on top of all other turtle creations.The fix:
The
screen._drawpolyends with external library code, most likely tkinter, so I won't be attempting to change its behavior. Generally, changing_drawturtle(),update,_update,tracer, or_tracerwould be inappropriate for such a bug, as doing such would be a large (potentially bug-creating) alteration for such a minor bug. Because of this, the fix should be with thecirclemethod itself.Due to how the
screen._drawpoly(..., top=True)call behaves in_drawturtle, we only actually want the call to be made for the current turtle that is drawing the circle. Unfortunately, theupdatemethod calls_drawturtlefor every turtle, so we can't avoid_drawturtlecalls for the turtles we don't want to draw. Fortunately,_drawturtlewon't actually callscreen._drawpoly(..., top=True)whenself._shown = Falseandself._hidden_from_screen = True. These are attributes that we CAN set from the circle method for any and every turtle object on the same screen. Thus, for every other turtle sharing the screen, we save the values of these two attributes, set them so they don't get redrawn, callself._tracer, and then restore the prior attribute values for all other turtle objects.Resolves#65276