4. 案例研究-接口程序

news2025/2/24 20:45:06

4. 案例研究-接口程序

本章通过一个案例研究, 来展示设计互相配合的函数的过程.
4.1 turtle 模块
创建一个文件mypolygon.py, 并输入如下代码:
import turtle
bob = turtle.Turtle()
print(bob)

# 这一句的作用是让画板停留, 等手动点击x关闭画板, 程序才结束.
# 否则程序执行完毕画板就关闭了, 执行速度非常快, 画板出来0.几秒就关闭.
turtle.mainloop()

turtle模块()提供一个叫作Turtle的函数(), 它会创建一个Turtle对象(戏称乌龟), 我们将其赋值到bob变量.

image-20230307121854578

这意味着bob变量引用着在turtle模块中定义的Turtle类型的一个对象.
mainioop告诉窗口去等待用户进行某些操作, 虽然现在除了关闭窗口之外, 并没有提供给用户多少有用的操作.
创建一个好Turtle对象之后, 就可以调用它的一个方法(method)来在窗口中移动.
方法和函数类似, 但是使用的语法略有不同, 操作箭头向前(方向默认从左往右)移动: bob.fd(100), 完整代码:
import turtle
bob = turtle.Turtle()
print(bob)

# 箭头向前移动100像素.
bob.fd(100)

# 让画板停留.
turtle.mainloop()

image-20230307123711321

这个方法fd和bob(tuetle对象)是关联的. 调用方法和发出一个请求类似: 请求bob向前移动, (箭头向前移动).
fd的参数是移动的距离, 以像素(pixel)为单位, 所以实际移动的距离依赖于屏幕的分辨率.
Turtle对象的其它方法:
bk: 用于前进和后退.
lt: 控制向左旋转的角度, 单位是度.
rt: 控制向右旋转的角度, 单位是度.

Turtle对象控制一只笔, 可以朝上和朝下, 若笔朝下, 则会制出走过的痕迹.
pu: 箭头朝上, 画笔没有痕迹.
pd: 箭头朝下, 画笔有痕迹,
import turtle

bob = turtle.Turtle()

px = 100  # 半径
bob.fd(px)  # 箭头向前移动100px

bob.pu()  # 笔尖朝上, 没有痕迹.
bob.fd(px)  # 箭头向前移动100px

bob.pd()  # 笔尖朝下, 没有痕迹.
bob.fd(px)  # 箭头向前移动100px

turtle.mainloop()

image-20230309011533245

编写一个程序, 创建Turtle对象, 先让箭头向前移动100px, 左转90, 在向前移动100px.
import turtle
bob = turtle.Turtle()
print(bob)

# 前进100px.
bob.fd(100)

# 箭头向左旋转90度
bob.lt(90)

# 箭头前进100px
bob.fd(100)

# 让画板停留.
turtle.mainloop()

image-20230307130139751

运行程序之后会看到这个箭头, 先向东走100px, 在向北走100px, 留下两个条线.
修改程序, 画出一个正方形:
import turtle


def lt_fd(angle, px):
    # 箭头向左旋转xxx度, 走 n px.
    bob.lt(angle)
    bob.fd(px)


# 实例对象.
bob = turtle.Turtle()

# 前进100px.
bob.fd(100)

# 箭头向左旋转90度, 走100px.
lt_fd(90, 100)
lt_fd(90, 100)
lt_fd(90, 100)

# 让画板停留.
turtle.mainloop()

image-20230307154003658

4.2 简单重复
你可会写下如下代码:
import turtle

bob = turtle.Turtle()

# 前进100px.
bob.fd(100)
# 箭头向左旋转90度.
bob.lt(90)

# 前进100px.
bob.fd(100)
# 箭头向左旋转90度.
bob.lt(90)

# 前进100px.
bob.fd(100)
# 箭头向左旋转90度.
bob.lt(90)

# 前进100px.
bob.fd(100)

# 让画板停留.
turtle.mainloop()

使用for语句, 可以更紧凑地实现同样功能, 把下面的例子运行一次:
for i in range(4):
	print('Hello!')
    
运行工具窗口显示:
Hello!
Hello!
Hello!
Hello!
for语句的语法和函数的定义类似, 它也有一个以冒号结束的语句头, 还有一个缩进的语句体.
语句体可以包含任意数量的语句.
for语句也被称为循环(loop), 因此执行流程会遍历语句体, 之后从语句体的最开头重新循环执行.

这是for语句的最简单用法, 后面我们会看到更多的用法.
但这样已经足够重写刚才的正方形程序了, 使用for循环绘制正方形的程序:
for i in range(4):
	# 前进100px.
    bob.fd(100)
    # 箭头向左旋转90度.
    bob.lt(90)
    
import turtle

bob = turtle.Turtle()

for i in range(4):
	# 前进100px.
    bob.fd(100)
    # 箭头向左旋转90度.
    bob.lt(90)
    
# 让画板停留.
turtle.mainloop()

这个版本的代码和之前的绘制正方形代码其实还稍有不同, 因为在最后一次循环, 它多做了一次左转.
多余的左转稍微多消耗了点时间, 但因为每次循环做的事情都一样, 也让代码更加简练,
程序执行完之后, 箭头回归到初始的位置, 并朝向初始相同的方向.
4.3 练习
下面是一系列使用Turtle的练习. 它们力求有趣, 但也包含着某些寓意(做练习时, 可以猜一下其寓意). 
1.1 写一个函数square, 接收一个形参t, 用来表示一个Turtle对象, 利用Turtle来画一个正方形.
1.2 写一个函数, 调用时, 传入的bob做为实参, 来调用square函数.
import turtle
# 实例turtle对象.
bob = turtle.Turtle()


# 画正方形的函数.
def square(t):
    for i in range(4):
        t.fd(100)
        t.lt(90)


# 调用函数, 将turtle对象作为实参进行传递.
square(bob)
# 让画板停留.
turtle.mainloop()

import turtle

# 实例turtle对象.
bob = turtle.Turtle()


# 画正方形的函数.
def square(t):
    for i in range(4):
        t.fd(100)
        t.lt(90)


def func(t):
    square(t)


# 调用函数, 将turtle对象作为实参进行传递.
func(bob)
# 让画板停留.
turtle.mainloop()

2. 给square函数再添加一个形参length, 修改内容, 
   保证正方形的长度是length, 并修改函数调用以提供这第二个实参, 最后提供不同的length值测试你的程序.
import turtle
# 实例turtle对象.
bob = turtle.Turtle()


# 画正方形的函数.
def square(t, length):
    for i in range(4):
        t.fd(length)
        t.lt(90)


# 调用函数, 将turtle对象作为实参进行传递.
square(bob, 100)
# 让画板停留.
turtle.mainloop()

3. 复制square函数,, 并命名为polygon, 再添加一个形参n并修改函数体, 以绘制一个正n变形.
   提示: 正n变形的拐角是360/n度, 是几变形就需要移动几次.
import turtle
# 实例turtle对象.
bob = turtle.Turtle()


# 画正n边形的函数.
def polygon(t, length, n):
    # 计算旋转的角度.
    angle = 360 / n

    # 循环n次.
    for i in range(n):
        t.fd(length)
        t.lt(angle)


# 调用函数, 将turtle对象作为实参进行传递.
polygon(bob, 100, 6)

# 让画板停留.
turtle.mainloop()

4. 写一个函数circle函数, 接收代表turtle的形参t, 以及表示半径的形参r, 
   并使用合适的长度和边数调用polygon画一个近似的圆, 使用不同的r值来测试你的函数. 
   提示: 思考圆的周长(circumference), 并保证: length * n = circumference.
import turtle
import math

bob = turtle.Turtle()


def polygon(t, n, length):
    angle = 360 / n
    for i in range(n):
        t.fd(length)
        t.lt(angle)


def circle(t, r):
    # 计算周长.
    circumference = 2 * math.pi * r
    # 周长计算其边数的估计值公式:
    n = int(circumference / 3) + 1
    # 角度 = 周长 / 边数.
    length = circumference / n
    polygon(t, n, length)


circle(bob, 10)
turtle.mainloop()

n = int(circumference / 3) + 1
此代码根据正多边形的周长计算其边数的估计值.
多边形的每一条边的长度相等, 因此多边形的总周长等于边数与每条边长度的乘积.
为了估计边数, 代码将周长除以每条边长度的近似值, 
该长度通过将周长除以3获得, 然后使用“+1”将结果值向上舍入到最接近的整数.

对于具有大量边的面, 生成的估计值可能不准确, 因为随着边数的增加, 边长的近似值将变得不准确.
但是, 对于边数相对较少的面, 估计值应相当准确.
5. 给circle函数写一个更通用的版本, 称为arc, 增加一个形参angle, 
   用来表示画的圆弧的大小, 这里angle的单位是度数, 所以当angle=360, 则会画一个整圆.
# 完整代码:
import turtle
import math

bob = turtle.Turtle()


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


# 多变形函数.
def polygon(t, n, length):
    # 计算角度
    angle = 360 / n
    polyline(t, n, length, angle)


# 圆弧函数.
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
    polyline(t, n, step_length, step_angle)


# 画圆函数
def circle(t, r):
	# 第三次参数为360则是一个圆.
    arc(t, r, 360)


circle(bob, 100)
turtle.mainloop()

4.4 封装
第一个练习要求把画正方形的代码放到一个函数定义中, 并将turtle对象bob作为实参传入, 调用该函数.
下面一个解答:
def square(t):
	for i in range(4):
		t.fd(100)
		t.lt(90)


square(bob)
最内侧的语句, fd和lt都缩进了两层, 表达它们在for语句的语句体内, 而for语句在函数定义的函数体内部.
最后一行, square(bob), 有重新从左侧开始而没有缩进, 这表明for语句和square函数的定义都已经结束.

在函数体内, t引用的turtle对象和bob引用的相同, 所有t.lt(90)和直接调用bob.lt(90)是一样的效果.
在这种情况下为什么不直接把形参写为bob呢? 原因是t可以是任何turtle对象, 而不仅仅是bob,
所有可以再新建一个turtle对象, 并将它作为参数传入到square函数:
alice = Turtle()
square(alice)

# 补全代码为:
import turtle

bob = turtle.Turtle()
alice = turtle.Turtle()


def square(t):
    for i in range(4):
        t.fd(100)
        t.lt(90)


square(bob)
square(alice)

turtle.mainloop()

把一段代码用函数包裹起来, 称为封装(encapsulation).
封装的一个好处是, 它给这个段代码一个有意义的名称, 增加了可读性.
另外一个好处是, 当重复使用这段代码时, 调用一个函数比复制粘贴代码要简易很多.
4.5 泛化
下一步是给square函数添加一个length参数, 这个是一个解决方案:
def square(t, length):
	for i in range(4):
		t.fd(length)
		t.lt(90)

square(bob, 100)

# 补全代码为:
import turtle

bob = turtle.Turtle()


def square(t, length):
    for i in range(4):
        t.fd(length)
        t.lt(90)


square(bob, 100)

turtle.mainloop()

给函数添加参数的过程称为泛化(generalization), 因为它会让函数变得更通用:
在之前的版本中, 正方形总是一个大小, 而形的版本中, 可以是任意大小(给length传递不同的值实现).

下一步也是一次泛化, 我们不再值绘制正方形, 而是可以绘制任意边数的多边形. 这里有一个答案:
def polygon(t, n, length):
	angle = 360 / n
	for i in range(n):
		t.fd(length)
		t.lt(angle)


polygon(bob, 7, 70)
这个例子绘制一个7边形, 边长为70.
如果函数的形参比较多, 很容易忘掉每一个参数具体是什么, 或者忘掉它们的顺序.
所有在Python中, 调用函数时可以加上形参的名称, 这样是合法的, 并且有时候会有帮助:
polygon(bob, n=7, length=70)

这些参数被称为关键字参数(keyword argument), 因为他们使用'关键字'的形式带上了形参的名称调用,
(请别while与def之类的Python关键字混淆).

这个语法使得程序更加可读, 它也同样提示了我们实参和形参的工作方式:
当调用函数时, 实参传入并赋值给形参.
# 补全代码为:
import turtle

bob = turtle.Turtle()


def polygon(t, n, length):
    angle = 360 / n
    for i in range(n):
        t.fd(length)
        t.lt(angle)


polygon(bob, 7, 70)
turtle.mainloop()

4.6 接口设计
下一步是写画圆的circle函数, 接收形参r, 表示圆的半径.
下面试一个简单的例子, 通过调用polygon函数画50边的多变形:
import math


def circle(t, r):
	circumference = 2 * math.pi * r
	n = 50
	length = circumference / n
	polygon(t, n, length)
    
第一行计算半径为r的圆的周长, 使用公式2πr, 因为需要使用math.pi, 所有需要导入math模块.
依照惯例, import语句一般都放在脚本的开头.

n是我们用于近似画圆的多边形的边数, 所有length是每个边的长度, 因此, Polygon画出一个50边形,
近似与一个半径为r的圆.
这个解决方案的缺点之一n是一个常量, 因此对于很大的圆, 多变形的边线太长, 
对于小圆, 我们又浪费时间去画过短的边线.
解决办法之一是泛化这个函数, 将上形参n, 这样可以给用户(调用circle函数的人)更多的控制选择,
但接口就不那么清晰整洁了.
函数的接口是如何使用它的概要说明: 它有哪些参数? 这个函数做什么? 它的返回值是什么?
我们说一个接口'整洁'(clen), 是它能够让调用者完成所想的事情, 而不需要处理多余的细节.

在这个例子里, r属于函数的接口, 因为它指定了所画的圆的基本属性.
相对地, n则不那么适合, 因为它说明的是如何画圆的细节信息.

所以与其弄乱接口, 不如在代码内部根据周长来选择合适的n值.
def circle(t, r):
   
	circumference = 2 * math.pi * r
	# 计算n的值. circumference /  的结果肯能是一个数, int()舍弃小数部分.
	n = int(circumference /  3) + 1
	# 计算length的值.
	length = circumferenc / n
	polygon(t, n, length)
	
显示多边形是一个接近 circumference /  3 + 1 的整型, 所有每个边长近似是3,
已经小到足够画出好看的圆形, 但由足够大到不影响画线效率, 并且可接收任何尺寸的圆.
4.7 重构
当写circle函数时, 可以复用polygon, 因为边数很多的正多边形, 是圆的很好近似(使用正多边形得到近似圆).
但是arc则并不那么容易对付, 我们不能使用polygon和circle来画圆弧.
换个方法, 可以复制一个polygons函数, 在通过修改得到arc函数, 结果如下:
def arc(t, r, angle):
	# 弧长
	arc_length = 2 * math.pi * 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)
        
这个函数的第二部分很像polygon的实现, 但如果不修改polygon的接口, 无法直接复用.
我们也可以泛化polygon函数以接受第三个参数表示圆弧的角度, 
但那样的话polygon(多边形)就是不合适的名称了!
所有, 我们将这个更泛化的行数称为polyline(多边线):
# 修改函数名.
def polyline(t, n, length, angle):
    for i in range(n):
    	t.fd(length)
        t.lt(angle)
        
现在我们可以重写polygon和arc, 让他们调用polylin:
def polygon(t , n, length):
    # 计算角度
    angle = 360 / n
    polyline(t, n, length, angle):

def arc(t, r, angle):
	# 弧长
	arc_length = 2 * math.pi * angle / 360
	# 计算边数
	n = int(arc_length / 3) + 1
	step_length = arc_length / n
	step_angle = angle / n
	polyline(t, n, step_length, step_angle)
    
最后, 我们可以重写circle, 改为调用erc:
def circle(t, r):
	arc(t, r, 360)
    
# 完整代码:
import turtle
import math

bob = turtle.Turtle()


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


# 多变形函数.
def polygon(t, n, length):
    # 计算角度
    angle = 360 / n
    polyline(t, n, length, angle)


# 圆弧函数.
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
    polyline(t, n, step_length, step_angle)


# 画圆函数
def circle(t, r):
	# 第三次参数为360则是一个圆.
    arc(t, r, 360)


circle(bob, 100)
turtle.mainloop()

这个过程-重构组织程序, 以改善接口, 提高代码复用被称为重构(refactoring).
在这个例子里, 我们注意到arc和polygon中有类似的代码, 因此我们把它们的共同之处'重构出来',
抽取到polyline函数中(指的是for循环语句).
如果我们早早计划, 可能会直接先写下polyline, 也就避免了重构, 
但实际上在工程开始时我们往往并没有足够的信息去完美的设计所有的接口. 
开始编码之后, 你会更了解面对的问题, 有时候, 重构正意味着你在编程中掌握了一些新的东西.
4.8 一个开发计划
开发计划(development plan)是写程序的过程, 本章的案例分析中, 我们使用的过程是'封装和泛化'.
这个过程的具体步骤是: 
1. 最开始写一些小程序, 而不需要函数定义.
2. 一旦程序成功运行, 识别出其中一段完整的部分, 将它封装到一个函数中, 并加以命名.
3. 泛化这个函数, 添加合适的形参.
4. 重复步骤1到步骤3, 直到得到一组可行的函数, 复制粘贴代码, 以免重复输入(以免重复调试).
   复制书写正确的代码, 不要手打了, 手打就无法避免出错...
5. 寻找可以使用重构来改善程序的机会.
   例如, 如果发现程序中几处地方有相似的代码, 可以考虑将它们抽取去来做一个合适的通用函数.

这个过程也是有一些缺点-我们会在后面看到其它方式, 但如果在开始编程时不清楚如何将程序分成适合的函数,
这样做会带来帮助. 这个方法能让你一边开发一边设计.
4.9 文档字符串
文档字符串(docstring)是在函数开头用来解释其接口的字符串(doc是'文档'documentation的缩写).
下面是一个实例:
def polylint(t, n. length, angle):
	"""
	用给定的长度和角度(以度为单位)绘制n个线段.
	t: turtle对象.
	n: 图形的边数.
	length: 每条边的长度.
	angle: 角度.
	"""
	for i in range(n):
		t.fd(length)
		t.lt(angle)
依照惯例, 所有的文档字符串都使用三引号括起来, 三引号字符串又称为多行字符串.
因为三引号允许字符串跨行表示.

文档字符串很简洁, 但已经包含了其它人需要知道的关于函数的基本信息. 
它简明地简洁地解释了函数是做什么的(而不涉及如何实现的细节), 
它解释了每个形参对函数行为的影响效果, 以及每个形参应有的类型(如果其类型并不显而易见).

编写这类文档是接口设计的重要部分, 一个设计良好的接口, 也应当很简单就能解释清楚.
如果你发现解释器一个函数很困难, 很可能表示该接口有改进的空间.
4.10 调试
函数的接口, 作用就像是函数和调用者之间签订的一个合同.
调用者同意提供某些参数, 而函数则同意使用这些函数做某种工作.

例如: polyline需要4个参数:
t: 必须是一个Turtle对象,
n: 必须是整数.
length: 应当是个正数.
angle: 则必须是一个数字, 并且按照度数类理解.

这些需求被称为前置条件, 因为它们应当在函数开始执行之前就保证为真.
相对地, 函数结束的时候需要满足的条件称为后置条件.
后置条件包含了函数预期的效果(如画出线段)以及任何副作用(如移动箭头, 或者引起其他改变).

满足前置条件是调用者的职责, 如果调用者违反了一个(文档说明清晰的!)前置条件,
因而导致函数没有正确运行, 则bug是在调用者, 而不再函数本身.

如果前置条件以及满足, 但后置条件没有满足, 那么bug就出现在函数本身.
如果前置和后置都定义清晰, 可以帮助调试.
4.11 术语表
方法(method): 与某个对象关联的一个函数, 使用句点表达式调用.

循环(loop): 程序中的一个片段, 可以重复运行.

封装(encapsulation): 将一组语句转换为函数定义的过程.

泛化(generalizatuon): 将一些不必要的具体值(如一个数字)替换为合适的通用参数或变量的过程.

关键词参数(Keyword argument): 调用函数时, 附带了参数名称(作为一个'关键词'来使用)的参数.

接口(interface): 描述函数如何使用的说明. 包含函数的名称, 以及形参与返回值的说明.

重构(refactoring): 修改代码并改善函数的接口以及代码质量的过程.

开发计划(development plan): 写程序的过程.

文档字符串(docstrinh): 在函数定义开始处出现的, 用于说明函数接口的字符串.

前置条件(precondition): 在函调用开始前应当满足的条件.

后置条件(postcondition): 在函数调用结束后应当满足的条件.

4.12 练习
1. 练习1
: https://github.com/AllenDowney/ThinkPython2/blob/master/code/polygon.py 下载本章的代码.
1. 画一个栈图来显示函数circle(bob, radius)运行时的程序状态.
   你可以手动计算, 或者在代码中添加一些pritn语句.
# 不去复制, 新手看不懂, 画下面这个也是一样的.
import math
import turtle

bob = turtle.Turtle()


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


def polygon(t, n, length):
    angle = 360.0 / n
    print('polygon', t, n, length, angle)
    polyline(t, n, length, angle)


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


def circle(t, r):
    print('cicle', t, r)
    arc(t, r, 360)


radius = 100  # 半径
circle(bob, radius)

# 等待用户关闭窗口
turtle.mainloop()

2023-03-09_01738

2. 4.7节中arc的函数并不准确, 因为使用多边形模拟近似圆, 总是会在真实的圆之外.
   因此, Turtle画完线之后会偏离真确的目标几个像素的地方, 
   我的解决方案例展示了一种方法可以减少这种错误的效果,
   阅读代码并考虑是否合理, 如果你自己画图, 可能会发现它是如何生效的. 
   * 不要去纠结这些东西, 目前没必要.
# 在画弧时,首先角度向左转了(step_angle / 2),即1/2近似多边形线段边的夹角值.
t.lt(step_angle/2)
polyline(t, n, step_length, step_angle)
# 结尾在进行一次转角.
t.rt(step_angle/2)
2. 练习2
写一组合适的通用函数, 用来画出下图所示的花图案(图在代码后面).
解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/flower.py
另外也需要: https://github.com/AllenDowney/ThinkPython2/blob/master/code/polygon.py
有兴趣可以看看, 百度好多详细的解说, 我就跳过了...
# arc.py

import math
import turtle

bob = turtle.Turtle()


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


def polygon(t, n, length):
    angle = 360.0 / n
    print('polygon', t, n, length, angle)
    polyline(t, n, length, angle)


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


def circle(t, r):
    print('cicle', t, r)
    arc(t, r, 360)


def petal(t, r, angle):
    for i in range(2):
        arc(t, r, angle)
        t.lt(180-angle)


def flower(t, n, r, angle):
    for i in range(n):
        petal(t, r, angle)
        t.lt(360.0/n)


def move(t, length):
    t.pu()
    t.fd(length)
    t.pd()


move(bob, -100)
flower(bob, 7, 60.0, 60.0)

move(bob, 100)
flower(bob, 10, 40.0, 80.0)

move(bob, 100)
flower(bob, 20, 140.0, 20.0)

bob.hideturtle()
turtle.mainloop()

# 等待用户关闭窗口
turtle.mainloop()

image-20230309114047282

3. 练习3
写一组合适的通用函数, 用来画出4-2所示的图形.
解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/pie.py
import math
import turtle


def draw_pie(t, n, r):
    polypie(t, n, r)
    t.pu()
    t.fd(r * 2 + 10)
    t.pd()


def polypie(t, n, r):
    angle = 360.0 / n
    for i in range(n):
        isosceles(t, r, angle / 2)
        t.lt(angle)


def isosceles(t, r, angle):
    y = r * math.sin(angle * math.pi / 180)

    t.rt(angle)
    t.fd(r)
    t.lt(90 + angle)
    t.fd(2 * y)
    t.lt(90 + angle)
    t.fd(r)
    t.lt(180 - angle)


bob = turtle.Turtle()

bob.pu()
bob.bk(130)
bob.pd()

# 绘制具有不同边数的多边形
size = 40
draw_pie(bob, 5, size)
draw_pie(bob, 6, size)
draw_pie(bob, 7, size)
draw_pie(bob, 8, size)

bob.hideturtle()
turtle.mainloop()

image-20230309114607269

4. 练习4
字母表中的字母可以是一些基本元素来构成, 如横线, 竖线, 以及一些曲线.
设计一个字母表, 可以使用最少的基本元素画出来, 并编写函数来画出字母.
你应当给每个字母单独写一个函, 名称为draw_a, draw_b等, 并把这些函数放到letter.py文件中.
可以从: https://github.com/AllenDowney/ThinkPython2/blob/master/code/typewriter.py 
下载一个'Turtle打字机'程序来帮你测试你写的代码.(这个对于新手来说可能不理解, 可以忽略调.)
解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/letters.py
依赖的模块文件: https://github.com/AllenDowney/ThinkPython2/blob/master/code/polygon.py

直接复制代码运行一次就得了, 有时间去研究, 没时间跳过, 不实用...
运行letters.py得到的图形:

image-20230309120211455

运行letter.py后需要按下按键上的字母, 它依据键入的字母画画, 得到的图形如下:

image-20230309120502172

5. 练习5
 http://en.wikipedia.org/wiki/Spiral 阅读关于螺旋线(spiral)的信息, (国外的网站访问不了).
接着编写一段程序画出阿基米德螺旋线(或者其它的某种螺旋线).
解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/spiral.py
import turtle


def draw_spiral(t, n, length=3, a=0.1, b=0.0002):
    """Draws an Archimedian spiral starting at the origin.
    Args:
      n: how many line segments to draw
      length: how long each segment is
      a: how loose the initial spiral starts out (larger is looser)
      b: how loosly coiled the spiral is (larger is looser)
    http://en.wikipedia.org/wiki/Spiral
    """
    theta = 0.0

    for i in range(n):
        t.fd(length)
        dtheta = 1 / (a + b * theta)

        t.lt(dtheta)
        theta += dtheta


# create the world and bob
bob = turtle.Turtle()
draw_spiral(bob, n=1000)

turtle.mainloop()

image-20230309120832404

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1820874.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring中的IOC

IOC(Inversion of Control,控制反转)是Spring框架核心概念之一。它是一种设计原则,用来实现对象的松耦合和依赖管理。在传统的编程中,对象负责创建或查找其依赖对象,而在IOC模式下,这些职责被移…

C++ | Leetcode C++题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; class Solution { public:int evalRPN(vector<string>& tokens) {int n tokens.size();vector<int> stk((n 1) / 2);int index -1;for (int i 0; i < n; i) {string& token tokens[i];if (token.length() >…

24年大一尺取练习(东北林业大学)

前言&#xff1a; 今天下午才刚看到oj上发了这次练习&#xff0c;我已经错过了截止时间&#xff0c;刚好不是很想复习六级&#xff0c;就把这次练习补了吧。 正文&#xff1a; Problem:A 尺取Language&#xff1a; #include<bits/stdc.h> using namespace std; const i…

如何把路由器设备的LAN口地址为三大私网地址

要将路由器的LAN口地址配置为三大私有IP地址范围之一&#xff08;10.0.0.0/8、172.16.0.0/12 或 192.168.0.0/16&#xff09;&#xff0c;我们需要访问路由器的管理界面并进行相应的设置。 下面是步骤&#xff1a; 连接到路由器&#xff1a; 连接到路由器的管理界面&#xf…

C++设计模式——Bridge桥接模式

一&#xff0c;桥接模式简介 桥接模式是一种结构型设计模式&#xff0c;用于将抽象与实现分离&#xff0c;这里的"抽象"和"实现"都有可能是接口函数或者类。 桥接模式让抽象与实现之间解耦合&#xff0c;使得开发者可以更关注于实现部分&#xff0c;调用…

谷粒商城实战(036 k8s集群学习2-集群的安装)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第343p-第p345的内容 k8s 集群安装 kubectl --》命令行操作 要进入服务器 而且对一些不懂代码的产品经理和运维人员不太友好 所以我们使用可视化…

【5.x】ELK日志分析

ELK日志分析 一、ELK概述 1、ELK简介 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将ElasticSearch、Logstash和Kiabana三个开源工具配合使用&#xff0c;完成更强大的用户对日志的查询、排序、统计需求。 一个完整的集中式日志系统&#xff0c;需要包含以下几个主…

Java | Leetcode Java题解之第149题直线上最多的点数

题目&#xff1a; 题解&#xff1a; class Solution {public int maxPoints(int[][] points) {int n points.length;if (n < 2) {return n;}int ret 0;for (int i 0; i < n; i) {if (ret > n - i || ret > n / 2) {break;}Map<Integer, Integer> map ne…

SpringBoot系列——使用Spring Cache和Redis实现查询数据缓存

文章目录 1. 前言2. 缓存2.1 什么是缓存2.2 使用缓存的好处2.3 缓存的成本2.4 使用Spring Cache和Redis的优点 3. Spring Cache基础知识3.1 Spring Cache的核心概念3.2 Spring Cache的注解3.2.1 SpEL表达式3.2.2 Cacheable3.2.3 CachePut3.2.4 CacheEvict 4. 实现查询数据缓存4…

eclipse创建maven项目

第一步&#xff1a;打开eclipse 我们选择java项目即可 点击finish即可 它会自动下载插件 然后在控制台上输入Y即可

C语言 | Leetcode C语言题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; int evalRPN(char** tokens, int tokensSize) {int n tokensSize;int stk[(n 1) / 2];memset(stk, 0, sizeof(stk));int index -1;for (int i 0; i < n; i) {char* token tokens[i];if (strlen(token) > 1 || isdigit(token[0])…

LeetCode | 28.找出字符串中第一个匹配项的下标 KMP

这是字符串匹配问题&#xff0c;朴素做法是两重遍历&#xff0c;依次从主串的i位置开始查看是否和模式串匹配&#xff0c;若不匹配就换下一个位置进行判断&#xff0c;直到找到或者遍历完&#xff0c;时间复杂度 O ( m n ) O(m \times n) O(mn) 还可以对主串进行处理&#xff…

Django 5 Web应用开发实战

文章目录 一、内容简介二、目录内容三、值得一读四、适读人群 一、内容简介 《Django 5 Web应用开发实战》集Django架站基础、项目实践、开发经验于一体&#xff0c;是一本从零基础到精通Django Web企业级开发技术的实战指南。《Django 5 Web应用开发实战》内容以Python 3.x和…

边坡监测规范:确保边坡工程安全稳定的专业准则

边坡工程是土木工程中不可或缺的一部分&#xff0c;其安全性直接关系到工程整体的质量与稳定性。因此&#xff0c;在边坡工程中实施有效的监测措施&#xff0c;遵循一系列专业的监测规范&#xff0c;对于预防边坡失稳、滑坡等灾害的发生&#xff0c;保障人民群众的生命财产安全…

Leetcode 力扣119. 杨辉三角 II (抖音号:708231408)

给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: rowIndex 3 输出: [1,3,3,1]示例 2: 输入: rowIndex 0 输出: [1]示例 3: 输入: rowIndex 1 输出: [1,1]提示…

Golang | Leetcode Golang题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; func evalRPN(tokens []string) int {stack : make([]int, (len(tokens)1)/2)index : -1for _, token : range tokens {val, err : strconv.Atoi(token)if err nil {indexstack[index] val} else {index--switch token {case ""…

Java | Leetcode Java题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; class Solution {public int evalRPN(String[] tokens) {int n tokens.length;int[] stack new int[(n 1) / 2];int index -1;for (int i 0; i < n; i) {String token tokens[i];switch (token) {case "":index--;stack…

【ElasticSearch】windows server 2019安装ES8.9.1 + kibana8.9.1 + IK分词器

目录 准备工作 ES Kibana IK 安装 es es访问测试 将es安装为系统服务 Kibana 配置es 运行kibana 访问测试 IK 补充 准备工作 ES8.9.1 kibana8.9.1 IK的版本最好要对应上&#xff01;&#xff01;&#xff01; ES es8.9.1&#xff1a; https://artifa…

[大模型]Phi-3-mini-4k-Instruct Lora 微调

本节我们简要介绍如何基于 transformers、peft 等框架&#xff0c;对 Phi-3-mini-4k-Instruct 模型进行 Lora 微调。Lora 是一种高效微调方法&#xff0c;深入了解其原理可参见博客&#xff1a;知乎|深入浅出 Lora。 这个教程会在同目录下给大家提供一个 nodebook 文件&#x…

简单了解RS485与RS232(UART)

简单了解RS485与RS232&#xff08;UART&#xff09; 一、UART和RS232、RS485的关系1、UART2、RS232/RS4853、RS232 与 RS485 的区别与联系 二、Modbus协议说明1、什么是协议2、Modbus协议说明3、Modebus通信过程4、Modbus存储区5、Modbus协议类型6、Modbus功能码 三、stm32HC-S…