有关turtle的相关使用请参考《python图形绘制库turtle中文开发文档及示例大全》
本篇文为turtle库的实现剖析,但不涉及 python 的 TK库。
开始
入口探寻
在turtle中,直走是使用 forward 或者 fd 函数;在本机安装好了 turtle 库后,在以下的目录下找到了 turtle.py 文件:
我们先从常规的方式从入口开始探究turtle库的基本实现;新建一个turtle对象:
tt=Turtle()
在文件中找到 class Turtle:
class Turtle(RawTurtle):
"""RawTurtle auto-creating (scrolled) canvas. When a Turtle object is created or a function derived from some Turtle method is called a TurtleScreen object is automatically created. """
_pen = None
_screen = None
def __init__(self,
shape=_CFG["shape"],
undobuffersize=_CFG["undobuffersize"],
visible=_CFG["visible"]):
if Turtle._screen is None:
Turtle._screen = Screen()
RawTurtle.__init__(self, Turtle._screen,
shape=shape,
undobuffersize=undobuffersize,
visible=visible)
从注释中可以的到此类将会自动创建 TurtleScreen
对象以及 canvas
,这一点在 __init__
方法中有代码过程;之后调用了 RawTurtle
的 __init__
创建 turtle的动画部分,实现如下:
screens = []
def __init__(self, canvas=None,
shape=_CFG["shape"],
undobuffersize=_CFG["undobuffersize"],
visible=_CFG["visible"]):
if isinstance(canvas, _Screen):
self.screen = canvas
elif isinstance(canvas, TurtleScreen):
if canvas not in RawTurtle.screens:
RawTurtle.screens.append(canvas)
self.screen = canvas
elif isinstance(canvas, (ScrolledCanvas, Canvas)):
for screen in RawTurtle.screens:
if screen.cv == canvas:
self.screen = screen
break
else:
self.screen = TurtleScreen(canvas)
RawTurtle.screens.append(self.screen)
else:
raise TurtleGraphicsError("bad canvas argument %s" % canvas)
screen = self.screen
TNavigator.__init__(self, screen.mode())
TPen.__init__(self)
screen._turtles.append(self)
self.drawingLineItem = screen._createline()
self.turtle = _TurtleImage(screen, shape)
self._poly = None
self._creatingPoly = False
self._fillitem = self._fillpath = None
self._shown = visible
self._hidden_from_screen = False
self.currentLineItem = screen._createline()
self.currentLine = [self._position]
self.items = [self.currentLineItem]
self.stampItems = []
self._undobuffersize = undobuffersize
self.undobuffer = Tbuffer(undobuffersize)
self._update()
创建完一个turtle对象后,调用一下 forward 函数画一根线段。
我们打开 turtle 文件,按照一般形式的函数定义,查询 forward 函数的定义:
从注释中了解到,调用函数可以使用 forward | fd ,参数为传入一个距离;具体使用方法请参考文章头标注的文章,在这里并不做太多解释。
在 forward 函数底部,发现调用了 _go 方法:self._go(distance)
。查看 _go 方法:
def _go(self, distance):
"""move turtle forward by specified distance"""
ende = self._position + self._orient * distance
self._goto(ende)
在 _go 方法中,传入了 距离,并且 ende 赋值为 self._position + self._orient * distance
,先搞懂 _position 、_orient 、distance 这几个成员是什么东西。
_go 方法位于 TNavigator 类中,在 TNavigator 的 init 方法中,使用了 reset 方法,reset方法中有 _position 、_orient 的初始化:
def reset(self):
"""reset turtle to its initial values Will be overwritten by parent class """
self._position = Vec2D(0.0, 0.0)
self._orient = TNavigator.START_ORIENTATION[self._mode]
我们再查看 Vec2D :
class Vec2D(tuple):
"""A 2 dimensional vector class, used as a helper class for implementing turtle graphics. May be useful for turtle graphics programs also. Derived from tuple, so a vector is a tuple! Provides (for a, b vectors, k number): a+b vector addition a-b vector subtraction a*b inner product k*a and a*k multiplication with scalar |a| absolute value of a a.rotate(angle) rotation """
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
def __add__(self, other):
return Vec2D(self[0]+other[0], self[1]+other[1])
def __mul__(self, other):
if isinstance(other, Vec2D):
return self[0]*other[0]+self[1]*other[1]
return Vec2D(self[0]*other, self[1]*other)
def __rmul__(self, other):
if isinstance(other, int) or isinstance(other, float):
return Vec2D(self[0]*other, self[1]*other)
def __sub__(self, other):
return Vec2D(self[0]-other[0], self[1]-other[1])
def __neg__(self):
return Vec2D(-self[0], -self[1])
def __abs__(self):
return (self[0]**2 + self[1]**2)**0.5
def rotate(self, angle):
"""rotate self counterclockwise by angle """
perp = Vec2D(-self[1], self[0])
angle = angle * math.pi / 180.0
c, s = math.cos(angle), math.sin(angle)
return Vec2D(self[0]*c+perp[0]*s, self[1]*c+perp[1]*s)
def __getnewargs__(self):
return (self[0], self[1])
def __repr__(self):
return "(%.2f,%.2f)" % self
查看 Vec2D 后得知,其实也就是一个元组;在查看 TNavigator 类:
class TNavigator(object):
"""Navigation part of the RawTurtle. Implements methods for turtle movement. """
START_ORIENTATION = {
"standard": Vec2D(1.0, 0.0),
"world" : Vec2D(1.0, 0.0),
"logo" : Vec2D(0.0, 1.0) }
DEFAULT_MODE = "standard"
DEFAULT_ANGLEOFFSET = 0
DEFAULT_ANGLEORIENT = 1
随后查看 TNavigator.START_ORIENTATION[self._mode]
,在 TNavigator
类中得知 _mode
为 standard
。此时 TNavigator.START_ORIENTATION[self._mode]
为 Vec2D(1.0, 0.0)
。
接下来查看 _goto 方法:
def _goto(self, end):
"""Move the pen to the point end, thereby drawing a line if pen is down. All other methods for turtle movement depend on this one. """
## Version with undo-stuff
go_modes = ( self._drawing,
self._pencolor,
self._pensize,
isinstance(self._fillpath, list))
screen = self.screen
undo_entry = ("go", self._position, end, go_modes,
(self.currentLineItem,
self.currentLine[:],
screen._pointlist(self.currentLineItem),
self.items[:])
)
if self.undobuffer:
self.undobuffer.push(undo_entry)
start = self._position
if self._speed and screen._tracing == 1:
diff = (end-start)
diffsq = (diff[0]*screen.xscale)**2 + (diff[1]*screen.yscale)**2
nhops = 1+int((diffsq**0.5)/(3*(1.1**self._speed)*self._speed))
delta = diff * (1.0/nhops)
for n in range(1, nhops):
if n == 1:
top = True
else:
top = False
self._position = start + delta * n
if self._drawing:
screen._drawline(self.drawingLineItem,
(start, self._position),
self._pencolor, self._pensize, top)
self._update()
if self._drawing:
screen._drawline(self.drawingLineItem, ((0, 0), (0, 0)),
fill="", width=self._pensize)
# Turtle now at end,
if self._drawing: # now update currentLine
self.currentLine.append(end)
if isinstance(self._fillpath, list):
self._fillpath.append(end)
###### vererbung!!!!!!!!!!!!!!!!!!!!!!
self._position = end
if self._creatingPoly:
self._poly.append(end)
if len(self.currentLine) > 42: # 42! answer to the ultimate question
# of life, the universe and everything
self._newLine()
self._update() #count=True)
在 goto_方法中,最开头的注释说明了该方法的作用“从当前的位置移动到传入的end参数坐标点,在移动的过程中,绘制出线段,并且所有的 turtle 绘制方法都基于这个 goto_方法”。goto_方法中,开始定义了一个元组 go_modes :
go_modes = ( self._drawing,
self._pencolor,
self._pensize,
isinstance(self._fillpath, list))
在go_modes 元组中,传入了 _drawing、_pencolor、_pensize,并且调用了 isinstance 方法判断 _fillpath 是否为 list;并且接下来构造了一个 undo_entry元组。判断 if self.undobuffer:
后,为空或者Null 则 self.undobuffer.push(undo_entry)
。之后为默认状态下的绘制方法:
if self._speed and screen._tracing == 1:
diff = (end-start)
diffsq = (diff[0]*screen.xscale)**2 + (diff[1]*screen.yscale)**2
nhops = 1+int((diffsq**0.5)/(3*(1.1**self._speed)*self._speed))
delta = diff * (1.0/nhops)
for n in range(1, nhops):
if n == 1:
top = True
else:
top = False
self._position = start + delta * n
if self._drawing:
screen._drawline(self.drawingLineItem,
(start, self._position),
self._pencolor, self._pensize, top)
self._update()
if self._drawing:
screen._drawline(self.drawingLineItem, ((0, 0), (0, 0)),
fill="", width=self._pensize)
其中 start为当前位置的坐标点,end是目标位置的坐标点,以上最主要的方法中最重要是:_drawline
,使用_drawline
传入了配置参数、坐标序列、笔颜色、绘制线的宽度以及 是否指定 polyitem
;(具体坐标序列的算法我没搞清楚,希望有知道的同学可以告诉我这是咋算的,是什么公式,谢谢!)。
查看 _drawline 的实现:
def _drawline(self, lineitem, coordlist=None,
fill=None, width=None, top=False):
"""Configure lineitem according to provided arguments: coordlist is sequence of coordinates fill is drawing color width is width of drawn line. top is a boolean value, which specifies if polyitem will be put on top of the canvas' displaylist so it will not be covered by other items. """
if coordlist is not None:
cl = []
for x, y in coordlist:
cl.append(x * self.xscale)
cl.append(-y * self.yscale)
self.cv.coords(lineitem, *cl)
if fill is not None:
self.cv.itemconfigure(lineitem, fill=fill)
if width is not None:
self.cv.itemconfigure(lineitem, width=width)
if top:
self.cv.tag_raise(lineitem)
以上文章暂未全部剖析实现,之后将会更新。