[英]Need help debugging the Python turtle module on Mac
我正在嘗試調試我在學生的舊 Mac 計算機上構建的圖形模塊的一些奇怪行為。 我試圖只復制下面的相關內容,但我想我的問題可能會回答為什么會這樣的問題。
所以,如果你運行下面的代碼,你會看到一個橢圓在一個矩形中跳來跳去。 粗略地說,它的工作方式是橢圓由大約 200 個點組成,這些點基本上充當近似橢圓的多邊形。 有趣的是,在我學生的 2016 年 macbook air 上存在嚴重的“繪圖”錯誤。 當 Shape 在屏幕上移動時,它會擦除當前繪制的 Shape 的像素,然后在新位置繪制 Shape。 我沒有親自實施任何線程,因此理論上應該在進行任何新繪圖之前完成所有取消繪圖。 然而,當這個橢圓在他們的計算機屏幕上來回跳動時,許多線段並沒有被正確地展開,但橢圓在它的新位置被正確地重繪了。 更有趣的是,這只發生在 class 的 2 名學生的筆記本電腦上,他們都恰好擁有舊的 macbook airs。
所以我不知道該怎么做。 我的第一個想法是,由於 turtle 沒有實現任何線程,而是建立在 tkinter 之上,因此 tkinter 可能使用了某種程度的線程,但我不完全確定到什么程度,我希望有人可能能夠比我 go 更快地通過整個 tkinter 的源代碼對此發表評論,以嘗試了解可能發生的事情,以及我可以做些什么來解決它。
或者,也許我完全錯了,這與那無關。 我會很感激任何想法。
import turtle
from typing import Union
import abc
import time
import math
turtle.tracer(False)
turtle.colormode(255)
class Window:
def __init__(self, title: str, width: int, height: int):
""" The constructor for the Window class.
Creates the turtle._Screen object _screen according to the given
title, width, and height parameters given.
Args:
title (str): A string to title the window.
width (int): An int to specify the window width.
height (int): An int to specify the window height.
"""
self._screen = turtle.Screen()
self._screen.title(title)
self._screen.setup(width+20, height+20)
def setBackground(self, color: Union[str, tuple[float, float, float]]):
""" Sets the background color of the screen.
Args:
color (str or (float, float, float)): The desired color of
the background.
"""
self._screen.bgcolor(color)
class Shape(abc.ABC):
_turt: turtle.Turtle
_drawn: bool
_points: list[tuple[float, float]]
_filled: bool
_center: float
_screen: turtle._Screen
def __init__(self, lineWidth: int,
lineColor: Union[str, tuple[int, int, int]] = None,
fillColor: Union[str, tuple[int, int, int]] = None):
""" This initializes the instance variables common to all shapes.
This includes:
self._turt: The turtle.Turtle object which draws the shape.
It has a pensize of lineWidth, a pencolor of
lineColor, and a fill color of fillColor.
self._drawn: A boolean which is True if and only if the shape
is currently drawn to the screen. It is False by
default.
self._filled: A boolean which is True if and only if the shape
will be filled in when drawn. It is True when
fillColor is given.
self._screen: A Screen which the shape will be drawn to.
self._points: A list of tuples containing the points which
comprise the shape.
Args:
lineWidth (int): The line width of the outer edge
lineColor (str or (int, int, int)): The color of the outer edge.
fillColor (str or (int, int, int)): The color to fill the shape
in.
"""
self._turt = turtle.Turtle()
self._turt.penup()
self._turt.hideturtle()
self._turt.pensize(lineWidth)
self._filled = False
if lineColor:
self._turt.pencolor(lineColor)
if fillColor:
self._turt.fillcolor(fillColor)
self._filled = True
self._drawn = False
self._screen = turtle.Screen()
self._points = []
def setFill(self, color: Union[str, tuple[int, int, int]]):
""" Configures the given shape to be filled with the given parameter
color.
Args:
color (str or (int, int int)): The color to fill in the shape
"""
if color:
self._filled = True
self._turt.fillcolor(color)
if self._drawn:
self.undraw()
self.draw()
@abc.abstractmethod
def _determinePoints(self):
""" Determines the underlying points of the shape.
To be implemented by inherited classes.
"""
def draw(self):
""" Draws the shape to the screen"""
if not self._drawn:
self._turt.goto(self._points[0])
self._turt.pendown()
if self._filled:
self._turt.begin_fill()
for point in self._points:
self._turt.goto(point)
if self._filled:
self._turt.end_fill()
self._turt.penup()
self._screen.update()
self._drawn = True
def undraw(self):
""" Undraws the shape from the screen"""
self._turt.clear()
self._drawn = False
def move(self, dx: float = 0, dy: float = 0):
""" Moves the shape in the x direction by dx and in the y direction by
dy
Args:
dx (float): The amount by which to move the shape to the
right
dy (float): The amount by which to move the shape up """
for i in range(len(self._points)):
self._points[i] = (self._points[i][0] + dx,
self._points[i][1] + dy)
self._center = (self._center[0] + dx, self._center[1] + dy)
if self._drawn:
self.undraw()
self.draw()
def rotate(self, dTheta: float = 0):
""" Rotates the shape counter-clockwise by the angle dTheta measured
in degrees.
Args:
dTheta (float): The number of degrees by which to rotate.
"""
for i in range(len(self._points)):
xOffset = self._points[i][0] - self._center[0]
yOffset = self._points[i][1] - self._center[1]
self._points[i] = (
self._center[0] + xOffset * math.cos(dTheta * 2*math.pi / 360)
- yOffset * math.sin(dTheta * 2*math.pi / 360),
self._center[1] + xOffset * math.sin(dTheta * 2*math.pi / 360)
+ yOffset * math.cos(dTheta * 2*math.pi / 360))
self._turt.left(dTheta)
if self._drawn:
self.undraw()
self.draw()
class Rectangle(Shape):
def __init__(self,
lowerLeftCorner: tuple[int, int], width: int, height: int,
lineWidth: int = 1,
lineColor: Union[str, tuple[int, int, int]] = None,
fillColor: Union[str, tuple[int, int, int]] = None):
""" The constructor for the Rectangle Class.
In addition to the tasks performed by the constructor for the Shape
class, this sets the lowerLeftCorner parameter of the shape, as well
as calling the _determinePoints method to construct the 4 defining
points of the rectangle.
Args:
lowerLeftCorner ((int, int)):
A point denoting the bottom left corner of the
rectangle
width (int): The width of the rectangle
height (int): The height of the rectangle
lineWidth (int): The penwidth of the outer edge
lineColor (str or (float, float, float)): The pencolor of the
outer edge
fillColor (str or (float, float, float)): The color used to fill
the shape """
Shape.__init__(self, lineWidth, lineColor, fillColor)
self.lowerLeftCorner = lowerLeftCorner
self.width = width
self.height = height
self._determinePoints()
def _determinePoints(self):
""" Assembles the 4 defining points of the Rectangle and saves them in
_points.
"""
self._points = [
self.lowerLeftCorner,
(self.lowerLeftCorner[0] + self.width,
self.lowerLeftCorner[1]),
(self.lowerLeftCorner[0] + self.width,
self.lowerLeftCorner[1] + self.height),
(self.lowerLeftCorner[0],
self.lowerLeftCorner[1] + self.height),
self.lowerLeftCorner]
self._center = (self.lowerLeftCorner[0] + self.width/2,
self.lowerLeftCorner[1] + self.height/2)
class Oval(Shape):
def __init__(self, center: tuple[float, float], width: int,
height: int, steps: int = 500, lineWidth: int = 1,
lineColor: Union[str, tuple[int, int, int]] = None,
fillColor: Union[str, tuple[int, int, int]] = None):
""" The constructor for the Oval Class.
In addition to the tasks performed by the constructor for the Shape
class, this sets the center of the shape, as well as calling the
_determinePoints method to construct the points of the ellipse. The
steps parameter specifies the number of points to use for the
construction of the outline.
Args:
center ((float, float)): A point denoting the center of the oval
width (float): The width of the oval
height (float): The height of the oval
steps (int): The number of points to create for the oval
lineWidth (int): The line width of the outer edge
lineColor (str or (int, int, int)): The pencolor of the outer
edge
fillColor (str or (int, int, int)): The color used to fill the
given shape
"""
Shape.__init__(self, lineWidth, lineColor, fillColor)
self._center = center
self._width = width
self._height = height
self._steps = steps
self._determinePoints()
def _determinePoints(self):
""" Populates _points with as many points as specified by steps via the
parametric equations for an ellipse."""
for i in range(self._steps):
self._points.append(
(self._center[0] +
self._width * math.cos(i*2*math.pi/self._steps),
(self._center[1] +
self._height * math.sin(i*2*math.pi/self._steps))))
self._points.append(self._points[0])
if __name__ == "__main__":
window = Window("Tester", 600, 600)
Rectangle((-200, -250), 400, 500, 5, fillColor="Green").draw()
oval = Oval((-50, -50), 100, 50, 200, 3, "Red", "Blue")
oval.draw()
direction = [3, 3]
rotation = 3
def updateDirection():
global direction, rotation
if max([*zip(*oval._points)][0]) >= 200:
direction[0] = -3
if direction[1] > 0:
rotation = -3
else:
rotation = 3
elif min([*zip(*oval._points)][0]) <= -200:
direction[0] = 3
if direction[1] > 0:
rotation = 3
else:
rotation = -3
if max([*zip(*oval._points)][1]) >= 250:
direction[1] = -3
if direction[0] > 0:
rotation = 3
else:
rotation = -3
elif min([*zip(*oval._points)][1]) <= -250:
direction[1] = 3
if direction[0] > 0:
rotation = -3
else:
rotation = 3
time.sleep(1)
while(True):
oval.move(direction[0], direction[1])
updateDirection()
oval.rotate(rotation)
time.sleep(1/65)
我無法在我的系統上重現您的問題,但通過代碼重新考慮了 turtle-wise。 我注意到的一件事是draw()
和undraw()
在它們的邏輯上不對稱,所以我修復了這個問題並引入了redraw()
來優化undraw();draw()
組合。
我還在代碼中注意到,一些海龜操作會獨立於代碼觸發_screen.update()
。 我用計時器事件替換了while True:
循環:
from turtle import Screen, _Screen, Turtle
from typing import Union, List, Tuple
from abc import ABC, abstractmethod
from math import sin, cos, pi
Color = Union[str, Tuple[float, float, float]]
class Window:
def __init__(self, title: str, width: int, height: int):
""" The constructor for the Window class.
Creates the turtle._Screen object _screen according to the given
title, width, and height parameters given.
Args:
title (str): A string to title the window.
width (int): An int to specify the window width.
height (int): An int to specify the window height.
"""
self._screen = Screen()
self._screen.title(title)
self._screen.setup(width+20, height+20)
self._screen.tracer(False)
class Shape(ABC):
_turtle: Turtle
_drawn: bool
_points: List[Tuple[float, float]]
_filled: bool
_center: float
_screen: _Screen
def __init__(self, lineWidth: int, \
lineColor: Color = None, \
fillColor: Color = None):
""" This initializes the instance variables common to all shapes.
This includes:
self._turtle: The Turtle object which draws the shape.
It has a pensize of lineWidth, a pencolor of
lineColor, and a fill color of fillColor.
self._drawn: A boolean which is True if and only if the shape
is currently drawn to the screen. False by default.
self._filled: A boolean which is True if and only if the shape
will be filled in when drawn. It is True when
fillColor is given.
self._screen: A Screen which the shape will be drawn to.
self._points: A list of tuples containing the points which
comprise the shape.
Args:
lineWidth (int): The line width of the outer edge
lineColor (Color): The color of the outer edge.
fillColor (Color): The color to fill the shape in.
"""
self._turtle = Turtle()
self._turtle.hideturtle()
self._turtle.penup()
self._turtle.pensize(lineWidth)
self._filled = False
if lineColor:
self._turtle.pencolor(lineColor)
if fillColor:
self._turtle.fillcolor(fillColor)
self._filled = True
self._drawn = False
self._screen = Screen()
self._points = []
def setFill(self, color: Color):
""" Configures the given shape to be filled with the given parameter color.
Args:
color (Color): The color to fill in the shape
"""
if color:
self._filled = True
self._turtle.fillcolor(color)
self.redraw()
@abstractmethod
def _determinePoints(self):
"""
Determines the underlying points of the shape.
To be implemented by inherited classes.
"""
def draw(self):
""" Draws the shape to the screen """
if not self._drawn:
self._turtle.goto(self._points[0])
if self._filled:
self._turtle.begin_fill()
self._turtle.pendown() # causes _screen.update()
for point in self._points:
self._turtle.goto(point)
self._turtle.penup()
if self._filled:
self._turtle.end_fill() # causes _screen.update()
self._screen.update()
self._drawn = True
def undraw(self):
""" Undraws the shape from the screen """
if self._drawn:
self._turtle.clear()
self._screen.update()
self._drawn = False
def redraw(self):
""" (Re)draws the shape to the screen """
if self._drawn:
self._turtle.clear()
self._turtle.goto(self._points[0])
if self._filled:
self._turtle.begin_fill()
self._turtle.pendown() # causes _screen.update()
for point in self._points:
self._turtle.goto(point)
self._turtle.penup()
if self._filled:
self._turtle.end_fill() # causes _screen.update()
self._screen.update()
def move(self, dx: float = 0, dy: float = 0):
""" Moves the shape in the x direction by dx and in the y direction by dy
Args:
dx (float): The amount by which to move the shape to the right
dy (float): The amount by which to move the shape up
"""
for i in range(len(self._points)):
self._points[i] = (self._points[i][0] + dx, self._points[i][1] + dy)
self._center = (self._center[0] + dx, self._center[1] + dy)
self.redraw()
def rotate(self, dTheta: float = 0):
""" Rotates the shape counter-clockwise by the angle dTheta measured in degrees.
Args:
dTheta (float): The number of degrees by which to rotate.
"""
for i in range(len(self._points)):
xOffset = self._points[i][0] - self._center[0]
yOffset = self._points[i][1] - self._center[1]
self._points[i] = ( \
self._center[0] + xOffset * cos(dTheta * 2*pi / 360) - yOffset * sin(dTheta * 2*pi / 360), \
self._center[1] + xOffset * sin(dTheta * 2*pi / 360) + yOffset * cos(dTheta * 2*pi / 360))
self._turtle.left(dTheta)
self.redraw()
def get_points(self):
return self._points
class Rectangle(Shape):
def __init__(self, \
lowerLeftCorner: Tuple[int, int], width: int, height: int, \
lineWidth: int = 1, \
lineColor: Color = None, \
fillColor: Color = None):
""" The constructor for the Rectangle Class.
In addition to the tasks performed by the constructor for the Shape
class, this sets the lowerLeftCorner parameter of the shape, as well
as calling the _determinePoints method to construct the 4 defining
points of the rectangle.
Args:
lowerLeftCorner ((int, int)): A point denoting the bottom left corner of the rectangle
width (int): The width of the rectangle
height (int): The height of the rectangle
lineWidth (int): The penwidth of the outer edge
lineColor (Color): The pencolor of the outer edge
fillColor (Color): The color used to fill the shape
"""
Shape.__init__(self, lineWidth, lineColor, fillColor)
self.lowerLeftCorner = lowerLeftCorner
self.width = width
self.height = height
self._determinePoints()
def _determinePoints(self):
""" Assembles the 4 defining points of the Rectangle and saves them in _points. """
self._points = [ \
self.lowerLeftCorner, \
(self.lowerLeftCorner[0] + self.width, self.lowerLeftCorner[1]), \
(self.lowerLeftCorner[0] + self.width, self.lowerLeftCorner[1] + self.height), \
(self.lowerLeftCorner[0], self.lowerLeftCorner[1] + self.height), \
self.lowerLeftCorner]
self._center = (self.lowerLeftCorner[0] + self.width/2, self.lowerLeftCorner[1] + self.height/2)
class Oval(Shape):
def __init__(self, center: Tuple[float, float], width: int, height: int, \
steps: int = 500, \
lineWidth: int = 1, \
lineColor: Color = None, \
fillColor: Color = None):
""" The constructor for the Oval Class.
In addition to the tasks performed by the constructor for the Shape
class, this sets the center of the shape, as well as calling the
_determinePoints method to construct the points of the ellipse. The
steps parameter specifies the number of points to use for the
construction of the outline.
Args:
center ((float, float)): A point denoting the center of the oval
width (float): The width of the oval
height (float): The height of the oval
steps (int): The number of points to create for the oval
lineWidth (int): The line width of the outer edge
lineColor (Color): The pencolor of the oute edge
fillColor (Color): The color used to fill the given shape
"""
Shape.__init__(self, lineWidth, lineColor, fillColor)
self._center = center
self._width = width
self._height = height
self._steps = steps
self._determinePoints()
def _determinePoints(self):
"""
Populates _points with as many points as specified by
steps via the parametric equations for an ellipse
"""
for i in range(self._steps):
self._points.append( \
(self._center[0] + self._width * cos(i*2*pi/self._steps), \
self._center[1] + self._height * sin(i*2*pi/self._steps)) \
)
self._points.append(self._points[0])
if __name__ == '__main__':
def updateDirection():
global direction, rotation
x, y = list(zip(*oval.get_points()))
if max(x) >= 200:
direction[0] = -3
rotation = -3 if direction[1] > 0 else 3
elif min(x) <= -200:
direction[0] = 3
rotation = 3 if direction[1] > 0 else -3
if max(y) >= 250:
direction[1] = -3
rotation = 3 if direction[0] > 0 else -3
elif min(y) <= -250:
direction[1] = 3
rotation = -3 if direction[0] > 0 else 3
def draw():
oval.move(*direction)
updateDirection()
oval.rotate(rotation)
screen.ontimer(draw, 1000//65)
screen = Screen()
window = Window("Tester", 600, 600)
Rectangle((-200, -250), 400, 500, 5, fillColor='Green').draw()
oval = Oval((-50, -50), 100, 50, 200, 3, 'Red', 'Blue')
oval.draw()
direction = [3, 3]
rotation = 3
draw()
screen.mainloop()
看看這對您的用戶是否有任何影響。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.