use turtle graphics with a learner.
+Automatically begin and end filling
+-----------------------------------
+
+Starting with Python 3.14, you can use the :func:`fill` :term:`context manager`
+instead of :func:`begin_fill` and :func:`end_fill` to automatically begin and
+end fill. Here is an example::
+
+ with fill():
+ for i in range(4):
+ forward(100)
+ right(90)
+
+ forward(200)
+
+The code above is equivalent to::
+
+ begin_fill()
+ for i in range(4):
+ forward(100)
+ right(90)
+ end_fill()
+
+ forward(200)
+
+
Use the ``turtle`` module namespace
-----------------------------------
Filling
| :func:`filling`
+ | :func:`fill`
| :func:`begin_fill`
| :func:`end_fill`
| :func:`ondrag`
Special Turtle methods
+ | :func:`poly`
| :func:`begin_poly`
| :func:`end_poly`
| :func:`get_poly`
| :func:`setworldcoordinates`
Animation control
+ | :func:`no_animation`
| :func:`delay`
| :func:`tracer`
| :func:`update`
... else:
... turtle.pensize(3)
+.. function:: fill()
+
+ Fill the shape drawn in the ``with turtle.fill():`` block.
+
+ .. doctest::
+ :skipif: _tkinter is None
+
+ >>> turtle.color("black", "red")
+ >>> with turtle.fill():
+ ... turtle.circle(80)
+
+ Using :func:`!fill` is equivalent to adding the :func:`begin_fill` before the
+ fill-block and :func:`end_fill` after the fill-block:
+
+ .. doctest::
+ :skipif: _tkinter is None
+
+ >>> turtle.color("black", "red")
+ >>> turtle.begin_fill()
+ >>> turtle.circle(80)
+ >>> turtle.end_fill()
+
+ .. versionadded:: next
.. function:: begin_fill()
Special Turtle methods
----------------------
+
+.. function:: poly()
+
+ Record the vertices of a polygon drawn in the ``with turtle.poly():`` block.
+ The first and last vertices will be connected.
+
+ .. doctest::
+ :skipif: _tkinter is None
+
+ >>> with turtle.poly():
+ ... turtle.forward(100)
+ ... turtle.right(60)
+ ... turtle.forward(100)
+
+ .. versionadded:: next
+
+
.. function:: begin_poly()
Start recording the vertices of a polygon. Current turtle position is first
Animation control
-----------------
+.. function:: no_animation()
+
+ Temporarily disable turtle animation. The code written inside the
+ ``no_animation`` block will not be animated;
+ once the code block is exited, the drawing will appear.
+
+ .. doctest::
+ :skipif: _tkinter is None
+
+ >>> with screen.no_animation():
+ ... for dist in range(2, 400, 2):
+ ... fd(dist)
+ ... rt(90)
+
+ .. versionadded:: next
+
+
.. function:: delay(delay=None)
:param delay: positive integer
import os
import pickle
import re
+import tempfile
import unittest
import unittest.mock
-import tempfile
from test import support
from test.support import import_helper
from test.support import os_helper
"""
+def patch_screen():
+ """Patch turtle._Screen for testing without a display.
+
+ We must patch the _Screen class itself instead of the _Screen
+ instance because instantiating it requires a display.
+ """
+ return unittest.mock.patch(
+ "turtle._Screen.__new__",
+ **{
+ "return_value.__class__": turtle._Screen,
+ "return_value.mode.return_value": "standard",
+ },
+ )
+
+
class TurtleConfigTest(unittest.TestCase):
def get_cfg_file(self, cfg_str):
turtle.TurtleScreen.save(screen, file_path, overwrite=True)
with open(file_path) as f:
- assert f.read() == "postscript"
+ self.assertEqual(f.read(), "postscript")
def test_save(self) -> None:
screen = unittest.mock.Mock()
turtle.TurtleScreen.save(screen, file_path)
with open(file_path) as f:
- assert f.read() == "postscript"
+ self.assertEqual(f.read(), "postscript")
+
+ def test_no_animation_sets_tracer_0(self):
+ s = turtle.TurtleScreen(cv=unittest.mock.MagicMock())
+
+ with s.no_animation():
+ self.assertEqual(s.tracer(), 0)
+
+ def test_no_animation_resets_tracer_to_old_value(self):
+ s = turtle.TurtleScreen(cv=unittest.mock.MagicMock())
+
+ for tracer in [0, 1, 5]:
+ s.tracer(tracer)
+ with s.no_animation():
+ pass
+ self.assertEqual(s.tracer(), tracer)
+
+ def test_no_animation_calls_update_at_exit(self):
+ s = turtle.TurtleScreen(cv=unittest.mock.MagicMock())
+ s.update = unittest.mock.MagicMock()
+
+ with s.no_animation():
+ s.update.assert_not_called()
+ s.update.assert_called_once()
+
+
+class TestTurtle(unittest.TestCase):
+ def setUp(self):
+ with patch_screen():
+ self.turtle = turtle.Turtle()
+
+ def test_begin_end_fill(self):
+ self.assertFalse(self.turtle.filling())
+ self.turtle.begin_fill()
+ self.assertTrue(self.turtle.filling())
+ self.turtle.end_fill()
+ self.assertFalse(self.turtle.filling())
+
+ def test_fill(self):
+ # The context manager behaves like begin_fill and end_fill.
+ self.assertFalse(self.turtle.filling())
+ with self.turtle.fill():
+ self.assertTrue(self.turtle.filling())
+ self.assertFalse(self.turtle.filling())
+
+ def test_fill_resets_after_exception(self):
+ # The context manager cleans up correctly after exceptions.
+ try:
+ with self.turtle.fill():
+ self.assertTrue(self.turtle.filling())
+ raise ValueError
+ except ValueError:
+ self.assertFalse(self.turtle.filling())
+
+ def test_fill_context_when_filling(self):
+ # The context manager works even when the turtle is already filling.
+ self.turtle.begin_fill()
+ self.assertTrue(self.turtle.filling())
+ with self.turtle.fill():
+ self.assertTrue(self.turtle.filling())
+ self.assertFalse(self.turtle.filling())
+
+ def test_begin_end_poly(self):
+ self.assertFalse(self.turtle._creatingPoly)
+ self.turtle.begin_poly()
+ self.assertTrue(self.turtle._creatingPoly)
+ self.turtle.end_poly()
+ self.assertFalse(self.turtle._creatingPoly)
+
+ def test_poly(self):
+ # The context manager behaves like begin_poly and end_poly.
+ self.assertFalse(self.turtle._creatingPoly)
+ with self.turtle.poly():
+ self.assertTrue(self.turtle._creatingPoly)
+ self.assertFalse(self.turtle._creatingPoly)
+
+ def test_poly_resets_after_exception(self):
+ # The context manager cleans up correctly after exceptions.
+ try:
+ with self.turtle.poly():
+ self.assertTrue(self.turtle._creatingPoly)
+ raise ValueError
+ except ValueError:
+ self.assertFalse(self.turtle._creatingPoly)
+
+ def test_poly_context_when_creating_poly(self):
+ # The context manager works when the turtle is already creating poly.
+ self.turtle.begin_poly()
+ self.assertTrue(self.turtle._creatingPoly)
+ with self.turtle.poly():
+ self.assertTrue(self.turtle._creatingPoly)
+ self.assertFalse(self.turtle._creatingPoly)
class TestModuleLevel(unittest.TestCase):
from os.path import isfile, split, join
from pathlib import Path
+from contextlib import contextmanager
from copy import deepcopy
from tkinter import simpledialog
'RawTurtle', 'Turtle', 'RawPen', 'Pen', 'Shape', 'Vec2D']
_tg_screen_functions = ['addshape', 'bgcolor', 'bgpic', 'bye',
'clearscreen', 'colormode', 'delay', 'exitonclick', 'getcanvas',
- 'getshapes', 'listen', 'mainloop', 'mode', 'numinput',
+ 'getshapes', 'listen', 'mainloop', 'mode', 'no_animation', 'numinput',
'onkey', 'onkeypress', 'onkeyrelease', 'onscreenclick', 'ontimer',
'register_shape', 'resetscreen', 'screensize', 'save', 'setup',
- 'setworldcoordinates', 'textinput', 'title', 'tracer', 'turtles', 'update',
- 'window_height', 'window_width']
+ 'setworldcoordinates', 'textinput', 'title', 'tracer', 'turtles',
+ 'update', 'window_height', 'window_width']
_tg_turtle_functions = ['back', 'backward', 'begin_fill', 'begin_poly', 'bk',
'circle', 'clear', 'clearstamp', 'clearstamps', 'clone', 'color',
'degrees', 'distance', 'dot', 'down', 'end_fill', 'end_poly', 'fd',
- 'fillcolor', 'filling', 'forward', 'get_poly', 'getpen', 'getscreen', 'get_shapepoly',
- 'getturtle', 'goto', 'heading', 'hideturtle', 'home', 'ht', 'isdown',
- 'isvisible', 'left', 'lt', 'onclick', 'ondrag', 'onrelease', 'pd',
- 'pen', 'pencolor', 'pendown', 'pensize', 'penup', 'pos', 'position',
- 'pu', 'radians', 'right', 'reset', 'resizemode', 'rt',
- 'seth', 'setheading', 'setpos', 'setposition',
- 'setundobuffer', 'setx', 'sety', 'shape', 'shapesize', 'shapetransform', 'shearfactor', 'showturtle',
- 'speed', 'st', 'stamp', 'teleport', 'tilt', 'tiltangle', 'towards',
- 'turtlesize', 'undo', 'undobufferentries', 'up', 'width',
+ 'fillcolor', 'fill', 'filling', 'forward', 'get_poly', 'getpen',
+ 'getscreen', 'get_shapepoly', 'getturtle', 'goto', 'heading',
+ 'hideturtle', 'home', 'ht', 'isdown', 'isvisible', 'left', 'lt',
+ 'onclick', 'ondrag', 'onrelease', 'pd', 'pen', 'pencolor', 'pendown',
+ 'pensize', 'penup', 'poly', 'pos', 'position', 'pu', 'radians', 'right',
+ 'reset', 'resizemode', 'rt', 'seth', 'setheading', 'setpos',
+ 'setposition', 'setundobuffer', 'setx', 'sety', 'shape', 'shapesize',
+ 'shapetransform', 'shearfactor', 'showturtle', 'speed', 'st', 'stamp',
+ 'teleport', 'tilt', 'tiltangle', 'towards', 'turtlesize', 'undo',
+ 'undobufferentries', 'up', 'width',
'write', 'xcor', 'ycor']
_tg_utilities = ['write_docstringdict', 'done']
return self._delayvalue
self._delayvalue = int(delay)
+ @contextmanager
+ def no_animation(self):
+ """Temporarily turn off auto-updating the screen.
+
+ This is useful for drawing complex shapes where even the fastest setting
+ is too slow. Once this context manager is exited, the drawing will
+ be displayed.
+
+ Example (for a TurtleScreen instance named screen
+ and a Turtle instance named turtle):
+ >>> with screen.no_animation():
+ ... turtle.circle(50)
+ """
+ tracer = self.tracer()
+ try:
+ self.tracer(0)
+ yield
+ finally:
+ self.tracer(tracer)
+
def _incrementudc(self):
"""Increment update counter."""
if not TurtleScreen._RUNNING:
"""
return isinstance(self._fillpath, list)
+ @contextmanager
+ def fill(self):
+ """A context manager for filling a shape.
+
+ Implicitly ensures the code block is wrapped with
+ begin_fill() and end_fill().
+
+ Example (for a Turtle instance named turtle):
+ >>> turtle.color("black", "red")
+ >>> with turtle.fill():
+ ... turtle.circle(60)
+ """
+ self.begin_fill()
+ try:
+ yield
+ finally:
+ self.end_fill()
+
def begin_fill(self):
"""Called just before drawing a shape to be filled.
self.undobuffer.push(("beginfill", self._fillitem))
self._update()
-
def end_fill(self):
"""Fill the shape drawn after the call begin_fill().
if self.undobuffer:
self.undobuffer.cumulate = False
+ @contextmanager
+ def poly(self):
+ """A context manager for recording the vertices of a polygon.
+
+ Implicitly ensures that the code block is wrapped with
+ begin_poly() and end_poly()
+
+ Example (for a Turtle instance named turtle) where we create a
+ triangle as the polygon and move the turtle 100 steps forward:
+ >>> with turtle.poly():
+ ... for side in range(3)
+ ... turtle.forward(50)
+ ... turtle.right(60)
+ >>> turtle.forward(100)
+ """
+ self.begin_poly()
+ try:
+ yield
+ finally:
+ self.end_poly()
+
def begin_poly(self):
"""Start recording the vertices of a polygon.