《Think Python》练习 4-1:本章示例代码栈图、停止点偏离思考

该博客主要讨论《Think Python》书中练习4-1的内容,包括绘制circle函数执行的栈图,分析arc函数在模拟圆形时的偏离问题。作者通过展开arc函数的for循环,提出先左转再画圆的策略来减少误差,并探讨了该方法的几何原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第4章 案例研究:接口设计

练习 4-1 本章示例代码栈图、停止点偏离思考

【习题 4.1.1】 画一个栈图来显示函数 circle(bob, radius) 运行时的程序状态。
《Think Python》第4章示例代码(仅circle相关,完整版点击):

"""This module contains a code example related to

Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com

Copyright 2015 Allen Downey

License: http://creativecommons.org/licenses/by/4.0/
"""

from __future__ import print_function, division

import math
import turtle


def polyline(t, n, length, angle):
    """Draws n line segments.

    t: Turtle object
    n: number of line segments
    length: length of each segment
    angle: degrees between segments
    """
    for i in range(n):
        t.fd(length)
        t.lt(angle)


def arc(t, r, angle):
    """Draws an arc with the given radius and angle.

    t: Turtle
    r: radius
    angle: angle subtended by the arc, in degrees
    """
    arc_length = 2 * math.pi * r * abs(angle) / 360
    n = int(arc_length / 4) + 3
    step_length = arc_length / n
    step_angle = float(angle) / n

    # making a slight left turn before starting reduces
    # the error caused by the linear approximation of the arc
    t.lt(step_angle/2)
    polyline(t, n, step_length, step_angle)
    t.rt(step_angle/2)


def circle(t, r):
    """Draws a circle with the given radius.

    t: Turtle
    r: radius
    """
    arc(t, r, 360)


# the following condition checks whether we are
# running as a script, in which case run the test code,
# or being imported, in which case don't.

if __name__ == '__main__':
    bob = turtle.Turtle()

    # draw a circle centered on the origin
    radius = 100
    bob.pu()
    bob.fd(radius)
    bob.lt(90)
    bob.pd()
    circle(bob, radius)

    # wait for the user to close the window
    turtle.mainloop()

【求解】 在加入 print 语句之后,发现 bob 的值是 <turtle.Turtle object at 0x10eb04a90>,而不是 turtle.Turtle()

所属函数变量
__ main__bob ——> <turtle.Turtle object at 0x10eb04a90>
__ main__radios ——> 100
circlet ——> <turtle.Turtle object at 0x10eb04a90>
circler ——> 100
arct ——> <turtle.Turtle object at 0x10eb04a90>
arcr ——> 100
arcangle ——> 360
polylinet ——> <turtle.Turtle object at 0x103ba5518>
polylinen ——> 160
polylinelength ——> 3.9269908169872414
polylineangle ——> 2.25
因为 优快云 的 Markdown 文字两侧连打2个下划线是加粗,所以main的前面多了个空格(强迫症勿怪)

【习题 4.1.2】 在 4.7 节中的 arc 函数并不准确,因为使用多边形模拟近似圆,总是会在真实的圆之外。因此,Turtle 画完线之后会停在偏离正确的目标几个像素的地方。我的解决方案里展示了一种方法可以减少这种错误的效果。阅读代码并考虑是否合理。如果你自己画图,可能会发现它是如何生效的。
【求解】 先对比代码有何不同
《4.7 节》的 arc 函数:

def arc(t, r, angle):
	arc_length = 2 * math.pi * r * angle / 360
	n = int(arc_length / 3) + 1
	step_length = arc_length / n
	step_angle = angle / n

	for i in range(n):
		t.fd(step_length)
		t.lt(step_angle)

《练习 4-1》的arc 函数:

def polyline(t, n, length, angle):
    """Draws n line segments.

    t: Turtle object
    n: number of line segments
    length: length of each segment
    angle: degrees between segments
    """
    for i in range(n):
        t.fd(length)
        t.lt(angle)


def arc(t, r, angle):
    """Draws an arc with the given radius and angle.

    t: Turtle
    r: radius
    angle: angle subtended by the arc, in degrees
    """
    arc_length = 2 * math.pi * r * abs(angle) / 360
    n = int(arc_length / 4) + 3
    step_length = arc_length / n
    step_angle = float(angle) / n

    # making a slight left turn before starting reduces
    # the error caused by the linear approximation of the arc
    t.lt(step_angle/2)
    polyline(t, n, step_length, step_angle)
    t.rt(step_angle/2)

《练习 4-1》示例代码给出的解决方案是“在画圆前轻微左偏”,理由是“错误是由弧的线性相似导致的”。

关于 弧的线性相似误差
《Think Python》又名《重拾三角函数》。
由于本人脱离学校太久,总之根据本人已经没法证明的三角函数求极限理论,当近似圆中每个小三角夹角足够小的时候,近似圆就越接近圆,真实的圆并不完全在近似圆之内或之外,真实圆的弧不停的跨过近似圆的边,像这样:
在这里插入图片描述
这种近似圆与真实圆的误差会导致多边形的角在真实圆之外,即每当开始或停止画直线的时候,乌龟会停在多边形的角上,即真实圆之外。

关于处理方案:

#左转半个step_angle
t.lt(step_angle/2)

#画一个近似圆
for i in range(n):
	t.fd(length)
    t.lt(angle)
    
#右转半个step_angle
t.rt(step_angle/2)

我将 arc 函数引用的 polyline 函数中的 for 循环展开了,可以先看出来处理方案的 arc 函数是先左转半个 step_angle 的角度,再画一个 完整的 近似圆,画完后再讲乌龟右转半个 step_angle 归位:
在这里插入图片描述
老实说我并不觉得这种 “再画圈前稍微左转一点点(making a slight left turn before starting reduces)”的骚操作除了让画出的近似圆比之前的右偏半个 step_angle 对于画出的多边形本身并没有任何影响,为何作者会认为这种操作减小了误差呢?虽然作者说了“如果你自己画图,可能会发现它是如何生效的”,但是我画了更理解不了……
我打算写完第四章作业后,发个帖子找人问问,如果有大佬指点的话,会更新在这里。
我回来了……
在做【练习 4-4:字母表】时发现,如果删除掉

#左转半个step_angle
t.lt(step_angle/2)
    
#右转半个step_angle
t.rt(step_angle/2)

仅保留

#画一个近似圆
for i in range(n):
	t.fd(length)
    t.lt(angle)

当你在画J的时候,如果先画一竖,再左移直径画一个半圆,你会发现,根本接不上……
代码:

#引入数学模块、乌龟模块
import math
import turtle

#调用乌龟画图、提高画弧速度
bob = turtle.Turtle()
bob.delya = 0.01

#多边线
def polyline(t, n, length, angle):
    for i in range(n):
        t.fd(length)
        t.lt(angle)

#弧
def arc(t, r, angle):
    arc_length = 2 * math.pi * r * abs(angle) / 360
    n = int(arc_length / 4) + 3
    step_length = arc_length / n
    step_angle = float(angle) / n
    #t.lt(step_angle/2)
    polyline(t, n, step_length, step_angle)
    #t.rt(step_angle/2)

#线段
def line(t, l):
    t.fd(l)
        
#test:J
def draw_j(t, l):
    t.rt(90)
    line(t, l)
    t.pu()
    t.rt(90)
    line(t, l/2)
    t.lt(90)
    t.pd()
    arc(t, l/4, 180)
    
draw_j(bob,200)
turtle.mainloop()

效果:
在这里插入图片描述
说明 t.lt(step_angle/2) 转的微小角度才是正确的,不转所画的弧线虽然尺寸没问题,但是位置以起点为中心左偏了 t.lt(step_angle/2) 角度。
加上这两行代码(解除注释)后:

#弧
def arc(t, r, angle):
    arc_length = 2 * math.pi * r * abs(angle) / 360
    n = int(arc_length / 4) + 3
    step_length = arc_length / n
    step_angle = float(angle) / n
    t.lt(step_angle/2)
    polyline(t, n, step_length, step_angle)
    t.rt(step_angle/2)

效果:
在这里插入图片描述
仔细想了下函数 arc(t, r, angle) 的画法,如果开头不左转 t.lt(step_angle/2) 的话,相直线向下画第1笔,再开始“转一个小角度 - 再画一笔”的循环:
在这里插入图片描述
这就造成了一个问题:终点没有跟起点水平(没有 t.lt(step_angle/2) 的终点是红色,有 t.lt(step_angle/2) 的终点是绿色),造成了这种效果:
在这里插入图片描述
仔细想了一下,数学原因是:由于我们是用很短的直线模拟的曲线,起点和终点的切线斜度就是第一根线和最后一根线的斜度,第一根直线的斜度是0、最后一根切线的斜度是 cot(step_angle),没办法像真正的弧线一样做到起点和终点的切线斜度相同,故需要匀一下,让起点多 step_angle/2,终点少 step_angle/2。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值