《Think Python 2e》作业实现合集(更新至第八章)


文章目录


这是什么?

这里是《Think Python 2e》作业实现 !在这里将记录《Think Python 2e》作业的练习记录、终端信息和结果分析。

  • 这是《Think Python 2e》哪个版本的作业?
    《Think Python:如何像计算机科学家一样思考》第二版。这里主要参考了一个中文网页版《Think Python 2e》中译本
  • 可以当成《Think Python 2e》参考答案吗?
    这里主要记录了我自己完成作业时所产生的成果及习题总结,基本未参考教材所提供的答案,未免有失规范,参考答案建议还是以 绿茶出版社官方代码 为准。
  • 不同的解释器版本结果不尽相同,这里用的哪个版本Python解释器?
    这里用了Python 3.8.6版解释器,部分用安卓Pydroid 4.01_arm64中的3.8.3版Python解释器,在线解释器用教程推荐的PythonAnywhere中的3.8版Python解释器。

《Think Python 2e》作业实现(一): 程序之道

习题1-1:在交互模式中故意犯错

【习题 1.1.1】print 函数中,如果漏掉一个括号,或者两个都漏掉,会发生什么?

情况1:漏掉左括号且print后无空格

  • 练习记录:
>>> print'Hello, World!')
  File "<stdin>", line 1
    print'Hello, World!')
         ^
SyntaxError: invalid syntax
>>> print'Hello, World!'
  File "<stdin>", line 1
    print'Hello, World!'
         ^
SyntaxError: invalid syntax
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: invalid syntax 语法错误:无效的语法
  • 结果分析:解释器把 print'Hello看成一个值了

情况2:漏掉左括号,print后有空格

  • 练习记录:
>>> print 'Hello, World!') 
  File "<stdin>", line 1
    print 'Hello, World!') 
          ^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print('Hello, World!'))?
>>> print 'Hello, World!'
  File "<stdin>", line 1
    print 'Hello, World!'
          ^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print('Hello, World!')?
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: Missing parentheses in call to ‘print’. Did you mean print(‘Hello, World!’)? 语法错误:调用时缺少括号
  • 结果分析:Python3 中 print 是一个函数、需要括号,所以这里特别说明缺少括号并给出了可能的建议

情况3:缺少右括号

  • 练习记录:
>>> print('Hello, World!'
... )
Hello, World!
  • 终端信息:
序号 终端信息 含义
1 等待继续输入
  • 结果分析:解释器未发现函数右括号,认为未输入完毕,等待继续输入,输入函数结尾的标志)后,语句执行完成、正确输出Hello, World!

【习题 1.1.2】 如果正尝试打印一个字符串,那么漏掉一个或所有引号,会发生什么?

情况1:缺少右引号

  • 练习记录:
>>> print('Hello, World!)
  File "<stdin>", line 1
    print('Hello, World!)
                        ^
SyntaxError: EOL while scanning string literal
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: EOL while scanning string literal 语法错误:扫描字符串时行终止
  • 结果分析:解释器从左往右读代码,字符串没有终止符,但是引用print函数的行却终止了,故“扫描字符串时行终止”

情况2:缺少左引号(含同时缺少右引号)

  • 练习记录:
>>> print(Hello, World!')
  File "<stdin>", line 1
    print(Hello, World!')
                      ^
SyntaxError: invalid syntax
>>> print(Hello, World!)
  File "<stdin>", line 1
    print(Hello, World!)
                      ^
SyntaxError: invalid syntax
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: invalid syntax 语法错误:无效的语法
  • 结果分析:解释器把World!看成一个值了,而这样的值的命名在语法中是无效的

情况3:左右括号都缺失且把不符合值命名中无效字符都删除

  • 练习记录:
>>> print(Hello,World)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'Hello' is not defined
>>> print(HelloWorld)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'HelloWorld' is not defined
  • 终端信息:
序号 终端信息 含义
1 NameError: name ‘Hello’ is not defined 命名错误:命名没有被定义
  • 结果分析:解释器在解析时把Hello或者HelloWorld解析成一个值的命名,但这个值没定义或者初始化

【习题 1.1.3】 可以使用一个负号来表示负数,如-2。如果在数字之前放一个正号,会发生什么?如果是2++2呢?

  • 练习记录:
>>> -2
-2
>>> +2++2
4

【习题 1.1.4】 在数学标记里,前置 0 是没有问题的,如 02。在 Python 中也这么做会发生什么?

  • 练习记录:
>>> 02
  File "<stdin>", line 1
SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers
>>> 3-02
  File "<stdin>", line 1
SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers
>>> -022
  File "<stdin>", line 1
SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers 语法错误:不允许在十进制整数用前导 0
  • 结果分析:解释器解析时不允许在十进制整数用前导 0

【习题 1.1.5】 如果在两个值之间不放任何操作符,会发生什么?

  • 练习记录:
>>> 2 4
  File "<stdin>", line 1
    2 4
      ^
SyntaxError: invalid syntax
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: invalid syntax 语法错误:无效的语法
  • 结果分析:数字后面加空格后,解释器就认为该数字结束了,应该继续输入有效的运算符例如 *
>>> 2 * 4
8

习题1-2:用Python 解释器做计算器

【习题1.2.1】 42分42秒一共有多少秒?

  • 练习记录:
>>> 42 * 60 + 42
2562

【习题1.2.2】 10公里可以换算成多少英里?提示:一英里等于1.61公里。

  • 练习记录:
>>> 10 / 1.61
6.211180124223602

【习题1.2.2】 如果你花42分42秒跑完了10公里,你的平均配速(pace)是多少(每英里耗时,分别精确到分和秒)?你每小时平均跑了多少英里(英里/时)?

  • 练习记录:
>>> (42 * 60 + 42) / (10 / 1.61) / 60
6.874700000000001
>>> .874700000000001 * 60
52.48200000000006
>>> (10  /1.61) / ((42 * 60 + 42) / 60 / 60)
8.727653570337614
  • 结果分析:配速和每小时跑的路程都是按英里算的结果,电子书如此要求,中译本实体书是要求按1000米(公里)算,结果会有差异

《Think Python 2e》作业实现(二): 变量、表达式和语句

习题2-1:在交互模式中故意犯错

【习题 2.1.1】 42 = n 合法吗?

  • 练习记录:
>>> 42 = n
  File "<stdin>", line 1
SyntaxError: cannot assign to literal
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: cannot assign to literal 不能赋值给文字
  • 结果分析:只能把后面的值赋值给前面的变量,而数字是无效的变量名

【习题 2.1.2】 那么 x = y = 1呢?

  • 练习记录:
>>> x = y = 1
>>> x
1
>>> y
1
  • 结果分析:同时为两个变量赋值是可以的

【习题 2.1.3】 有些语言中,每个语句都需要以分号(;)结尾。如果你在 Python 语句的结尾放一个分号,会有什么情况?

  • 练习记录:
>>> x = y = 1
>>> print(x);
1
>>> print(x); print(y)
1
1
  • 结果分析:Python语句用分号(;)结尾,解释器就解析成分号后的内容为另一个语句

【习题 2.1.4】 如果在语句结尾放的是句号(.)呢?

  • 练习记录:
>>> print(x); print(y).
  File "<stdin>", line 1
    print(x); print(y).
                     ^
SyntaxError: invalid syntax
  • 终端信息:
序号 终端信息 含义
1 SyntaxError: invalid syntax 无效语法
  • 结果分析:Python语句中不能用句号(.)结尾

【习题 2.1.5】 在数学标记中,对于 x 乘以 y,可以这么表达:xy。在Python中这样尝试会有什么结果?

  • 练习记录:
>>> x = 2
>>> y = 4
>>> x * y
8
>>> xy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'xy' is not defined
>>> x y
  File "<stdin>", line 1
    x y
      ^
SyntaxError: invalid syntax
  • 终端信息:
序号 终端信息 含义
1 NameError: name ‘xy’ is not defined xy未定义
2 SyntaxError: invalid syntax 无效语法
  • 结果分析:解释器把xy 解析成一个新的未定义或者赋值的值,而把x y解析成两个值,所以是无效的语法

习题2-2:用Python解释器做计算器

【习题 2.2.1】 半径为 r r r 的球体的体积是 ( 4 / 3 ) π r 3 π r ^ 3 πr3。半径为 5 的球体体积是多少?

  • 练习记录:
>>> (4 / 3) * 3.14 * 5**3
523.3333333333334

【习题 2.2.2】 假设一本书的定价是24.95美元,但是书店打了40%的折扣(6折)。运费是一本3美元,每加一本加75美分。60本书的总价是多少?

  • 练习记录:
>>> 24.95 * ((100 - 40) / 100) * 60 + 3 + .75*59
945.4499999999999

【习题 2.2.3】 如果我上午6:52离开家, 以放松跑的速度跑1英里(每英里耗时8分15秒),再以 节奏跑的速度跑3英里(每英里耗时7分12秒),之后又以放松跑的速度跑1英里,我什么时候回到家吃早饭?

  • 练习记录:
>>> 6 * 3600 + 52 * 60 + (8 * 60 + 15) + (7 * 60 + 12) * 3 + (8 * 60 + 15)
27006
>>> 27006 // 3600
7
>>> (27006 - 7 * 3600) // 60
30
>>> 27006 - 7 * 3600 - 30 * 60
6
  • 结果分析:
    • 到家吃早饭时间为7时30分6秒
    • //为整除运算符

《Think Python 2e》作业实现(三): 函数

习题3-1: 让最后一个字母在第70列上

【习题 3.1】 输入长度<70的任意字符串,让其最后一个字母在第70列上

  • 练习记录:
def right_justify(s):
	l=len(s)
	print(' '*(70-l)+s)
	
right_justify('monty')
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_1.py
                                                                 monty
  • 结果分析:合理使用字符串拼接(string concatenation)和重复;另外,Python提供了一个名叫 len 的内建函数,可以返回一个字符串的长度

习题3-2:在函数中调用另一个函数

函数对象是一个值,可以将它赋值给变量,或者作为其他函数实参传递,例如,do_twice 是一个函数,接收一个函数对象作为实参,并调用它两次:

def do_twice(f):
    f()
    f()
    

下面是一个使用 do_twice 来调用一个 print_spam 函数两次的示例:

def print_spam():
    print('spam')
    
do_twice(print_spam)

【习题 3.2.1】 将这个示例存入脚本并测试它

  • 练习记录:
def do_twice(f):
   f()
   f()
   
def print_spam():
   print('spam')
   
do_twice(print_spam)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_2_1.py
spam
spam
  • 变形练习:
def do_twice(f, g):
    f()
    g()
	
def print_spam():
    print('spam')
	
def print_bread():
	print('bread')
	 
do_twice(print_spam, print_bread)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_2_1.py
spam
bread
  • 结果分析:函数能以另一个(或者几个)函数作为实参
    【习题 3.2.2】 修改do_twice,使其接受两个实参,一个是函数对象,另一个是值;然后调用这一函数对象两次,将那个值传递给函数对象作为实参
def do_twice(f, s):
	f(s)
	f(s)
	
def print_s(s):
	print(s)
	
do_twice(print_s, 'bread')
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_2_2.py
bread
bread
  • 结果分析:被调用的函数有实参,那么调用时必须提供该实参,这里就是函数 f(s) 中的 s
  • 变形练习:
def do_twice(f, s, x):
	f(s, x)
	f(s, x)
	
def print_s(s, x):
	print(s + ' and ' + x)
	
do_twice(print_s, 'bread', 'spam')
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_2_2.py
bread and spam
bread and spam

【习题 3.2.4】 从本章前面一些的示例中,将 print_twice 函数的定义复制到脚本中;使用修改过的do_twice,调用print_twice两次,将’spam’传递给它作为实参

  • 练习记录:
def print_twice(s):
	print(s,end=' ')
	print(s)
	
def do_twice(f,s):
	f(s)
	f(s)

do_twice(print_twice, 'spam')
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_2_4.py
spam spam
spam spam

【习题 3.2.5】 定义一个名为do_four的新函数,其接受一个函数对象和一个值作为实参; 调用这个函数对象四次,将那个值作为形参传递给它; 函数体中应该只有两条语句,而不是四条

  • 练习记录:
#定义函数 print_spam 以供调用
def print_spam(x):
	print(x)

#定义函数 do_twice , 调用两次 print_spam 实现打印 x 两次
def do_twice(f,x):
	f(x)
	f(x)
		
#定义函数 do_four , 调用 do_twice 两次,实现打印 x 四次  
def do_four(f, x):
	do_twice(f, x)
	do_twice(f, x)

#执行 do_four(f, x)
do_four(print_spam, 'spam')
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_2_5.py
spam
spam
spam
spam
  • 结果分析:语句do_four(print_spam, 'spam') 把实参 print_spam 和 ‘spam’ 赋给函数 do_four , 函数 do_four 中 语句do_twice(f, x)把实参 print_spam 和 ‘spam’ 赋给函数 do_twice 两次 ,函数 do_twice 中语句 f(x)输入实参 print_spam 和 ‘spam’ 各打印 spam 两次

习题3-3:用函数画网格

【习题 3.3.1】 画2X2网格

  • 练习记录:
"""
print(str, end=""),下个打印语句不换行
"""
def print_xline_one():
	print("+---------",end="")
	
def print_xline_twice():
	print_xline_one()
	print_xline_one()
	
def print_xline():
	print_xline_twice()
	print("+")
	
def print_vline_one():
	print("|         |         |")
	
def print_vline_twice():
	print_vline_one()
	print_vline_one()

def print_vline():
	print_vline_twice()
	print_vline_twice()
	
def print_grid_one():
	print_xline()
	print_vline()

def print_grid_twice():
	print_grid_one()
	print_grid_one()
	
def print_grid_2():
	print_grid_twice()
	print_xline()
	
print_grid_2()
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_3_1.py
+---------+---------+
|         |         |
|         |         |
|         |         |
|         |         |
+---------+---------+
|         |         |
|         |         |
|         |         |
|         |         |
+---------+---------+

【习题 3.3.2】 画4X4网格

  • 练习记录:
"""
print(str, end=""),下个打印语句不换行
"""
def print_xline_one():
	print("+---------",end="")
	
def print_xline_twice():
	print_xline_one()
	print_xline_one()
	
def print_xline_four():
	print_xline_twice()
	print_xline_twice()
	
def print_xline():
	print_xline_four()
	print("+")
	
def print_vline_one():
	print("|         |         |         |         |")
	
def print_vline_twice():
	print_vline_one()
	print_vline_one()
	
def print_vline():
	print_vline_twice()
	print_vline_twice()

def print_grid_one():
	print_xline()
	print_vline()

def print_grid_twice():
	print_grid_one()
	print_grid_one()
	
def print_grid_four():
	print_grid_twice()
	print_grid_twice()
	
def print_grid_4():
	print_grid_four()
	print_xline()
	
print_grid_4()
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_3_2.py
+---------+---------+---------+---------+
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
+---------+---------+---------+---------+
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
+---------+---------+---------+---------+
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
+---------+---------+---------+---------+
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
|         |         |         |         |
+---------+---------+---------+---------+
  • 变形练习:
"""print(str, end=""),下个打印语句不换行
"""
def print_xline_one():
	print("+---------",end="")
	
def print_xline(x):
	for i in range(x):
		print_xline_one()
	
	print('+')
	
def print_vline_one():
	print("|         ",end=(''))
	

def print_vline(x):
	for i in range(x):
		print_vline_one()
	
	print('|')

def print_vlines(x):
	for i in range(4):
		print_vline(x)
		
		
def print_grid(x):
	print_xline(x)
	print_vlines(x)
		
def print_grid_x(x):
	for i in range(x):
		print_grid(x)
		
	print_xline(x)
	
print_grid_x(6)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\3_3_3.py
+---------+---------+---------+---------+---------+---------+
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
+---------+---------+---------+---------+---------+---------+
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
+---------+---------+---------+---------+---------+---------+
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
+---------+---------+---------+---------+---------+---------+
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
+---------+---------+---------+---------+---------+---------+
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
+---------+---------+---------+---------+---------+---------+
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
|         |         |         |         |         |         |
+---------+---------+---------+---------+---------+---------+
  • 结果分析:print 函数执行后默认会自动换行,但是可以阻止这个行为,只需要像下面这样,print函数的参数以end=''结尾,如下例,输出结果未发生换行:
print('+', end='')
print('-')
+-

《Think Python 2e》作业实现(四): 案例研究—接口设计

习题4-1:画函数堆栈图

【习题4.1.1】 画函数 circle 重构后的堆栈图

import math
import turtle

bob = turtle.Turtle()

def polygon_arc(t, length, n, angle):
	m = int(n * angle / 360)
	for i in range(m):
		t.fd(length)
		t.lt(360 / n)

def arc(t, r, angle):
    circumference = 2 * math.pi * r
    n = int(circumference / 3) + 1
    length = circumference / n
    polygon_arc(t, length, n , angle)

def circle(t, r):
	arc(t, r, 360)
	
circle(bob, 100)

turtle.mainloop()
  • 练习记录:
    在这里插入图片描述
  • 结果分析:
    • 每个函数用一个栈帧(frame)表示,每个栈帧一个线框
    • 函数名写在线框外,例如本题中的函数名 arc
    • 形参及函数内部变量写在线框内:
      • 形参及其指向实参值,例如函数 arc 的形参 t、 r 和 angle 及其指向实参值
      • 函数内部变量及其指向值,例如函数 arc 内部变量 n、length 和 circumference 及其指向值

【习题4.1.2】 让画的圆左偏转一个角度以使其(位置)更精确

  • 练习记录:
import math
import turtle

bob = turtle.Turtle()
tom = turtle.Turtle()
jack = turtle.Turtle()

def polygon_arc(t,length,n,angle):
	m = int(n*angle/360)
	for i in range(m):
		t.fd(length)
		t.lt(360/n)

def arc(t, r, angle):
    circumference = 2 * math.pi * r
    n = int(circumference / 30) + 1
    length = circumference / n
    polygon_arc(t, length,n, angle)
	
def arc_left(t, r, angle):
	circumference = 2 * math.pi * r
	n = int(circumference / 30) + 1
	length = circumference / n
	t.lt(angle/n/2)                           # 左偏画笔每笔偏转角一半,如教程答案代码所示
	polygon_arc(t, length,n, angle)
	t.rt(angle/n/2)                          # 右偏画笔每笔偏转角一半,消除画笔左偏引起的改变
	
def arc_circle(t, r, angle):
    circumference = 2 * math.pi * r
    n = int(circumference) + 1        # 以近似每像素画一笔的精度,画出更精细的圆形线性近似即标准圆
    length = circumference / n
    polygon_arc(t, length,n, angle)
	
def circle(t, r):
	arc(t, r, 360)
	
def circle_left(t, r):
	arc_left(t, r, 360)
	
def circle_circle(t, r):
	arc_circle(t, r, 360)

bob.pencolor(0,0,1)
circle(bob, 100)

tom.pencolor(1,0,0)
circle_left(tom, 100)

jack.pencolor(0,1,0)
circle_circle(jack, 100)

turtle.mainloop()

在这里插入图片描述

  • 结果分析:
    • 所谓标准圆,实为更精细的圆形线性近似(linear approximation)
    • 画三个圆即标准圆、画笔未提前偏转的圆及画笔提前左偏转一定角度的圆以做对比,以体现画笔提前左偏转一定角度后的效果;为便于对比,画不同的圆用不同颜色的画笔,为此找到了 Turtle 对象的方法 pencolor() ,通过bob.pencolor(0,0,1)语句使用蓝色画笔,通过tom.pencolor(1,0,0)语句使用红色画笔,通过jack.pencolor(0,1,0)语句使用绿色画笔
    • 从所画三个圆可见,调用函数 circle 所画的圆形线性近似(蓝色圆)在标准圆(绿色圆)的右侧、比标准圆右偏了一个角度
    • 调用重构(使画笔提前左偏画圆时每笔所须偏转的角度的一半)后的函数 circle_left 所画的圆形线性近似(红色圆)与标准圆基本重合,说明画笔提前左偏转一定角度所画的圆整体向左偏转了一定的角度,确实如题所说(位置)更精确
    • 从这个题可见,函数 circle 所使用的方法有误差,改用直接计算小乌龟所画的弦线的长度经检验更准确,就不会有位置偏差;方法、算法其实比计算机本身计算的精确度、算力更重要,对结果影响更大

习题4-2:用函数绘制花朵

【习题】 编写比较通用的一个可以画出像教材图4-1中那样花朵的函数集

  • 练习记录:
import math
import turtle	

bob = turtle.Turtle()
tom = turtle.Turtle()                # 为察看花瓣位置,创建 tom 等其他小乌龟以画坐标线
jack = turtle.Turtle()
sam = turtle.Turtle()
john = turtle.Turtle()

def flower(t, L, radio, n):
	""" 
	用 Turtle 对象的 rt() 方法使画笔先右偏一个角度,使得第一个花瓣水平放置; 
	右偏的角度根据推算为花瓣边缘弧线所对圆心角(常规设为 2a )的一半,即 a ; 
	L 为弦长,radio 为花瓣边缘弧线曲率(弧长与弦长的比值),n 为花瓣数
	"""
	t.rt(alfa(L, radio) / math.pi * 180)
	for i in range(n):
		petal(t, L, radio)
		t.lt(360 / n)
		
def alfa(L, ratio):
	""" 
	迭代法计算弧线所在圆的半径 r 并据此计算圆心角的一半即 a 的值,迭代法计算公式网上查得
	"""
	r = L / 2                                            # r 为圆半径,初值赋为 L/2(不得小于此值)
	C = L * ratio                                 # C 为弧线长度
	for i in range(100000):
		r = (1+(L-2*r*math.sin(C/(2*r)))/(L-C*math.cos(C/(2*r)))) * r
	a = math.asin(( L / 2 ) / r)              # a 为圆心角一半
	return a                 # 取得函数 alfa 的返回值
	
def petal(t, L, ratio):
	C = L * ratio
	a = alfa(L, ratio)
	arc(t, a, C)
	t.lt(180 - (a * 2) / math.pi *180)     # 小乌龟折返所偏转的角度根据几何知识计算而得,为180-2a
	arc(t, a, C)
	t.lt(180 - (a * 2) / math.pi *180)    # 小乌龟偏转到画花瓣起始位置,偏转角度同样为 180-2a
	
def arc(t, a, C):
	n = int(C / 2) + 1            # n 为画的笔数,约2个像素画一笔
	step_arc = C / n
	step_alfa = ((a * 2) / math.pi * 180) / n
	t.lt(step_alfa / 2)      # 如习题4-1-2答案代码所示,提前左偏半个每笔偏转角度以使位置更精确
	for i in range(n):
		t.fd(step_arc)
		t.lt(step_alfa)
	t.rt(step_alfa / 2)
	
jack.fd(200)
tom.lt(180)
tom.fd(200)
sam.lt(90)
sam.fd(200)
john.rt(90)
john.fd(200)

flower(bob, 150, 1.03, 12)

bob.ht()                 # 隐藏 bob ,使得花朵更唯美 

turtle.mainloop()

在这里插入图片描述

  • 结果分析:
    • 实现了可通过输入不同实参设置花瓣数、花瓣长度和花瓣曲率(弧长与弦长比),比较通用
    • 迭代法是计算机科学中的一个常用算法,这里的 a (圆心角一半)即用此法算得
    • 函数返回值语句return a用于定义函数 alfa 的返回值
    • 使用了 Turtle 对象的方法 ht() ,以使小乌龟隐藏、花朵更唯美
    • 切记圆心角角度 degree 与弧度 radian 的关系:
      • degree = radian / math.pi * 180
      • Turtle对象的 lt() 与 rt() 方法接受角度degree
      • math模块中的函数如 math.sin 默认接受弧度 radian 实参

习题4-3:用函数绘制饼图

【习题】 编写比较通用的一个可以画出教材图4-2中那样图形的函数集

  • 练习记录:
import math
import turtle

bob = turtle.Turtle()

def pie_chart(t, l, n):
	for i in range(n):        # 默认 i 从0开始计数,这影响下面两个语句的顺序
		t.lt(360 / n * i)
		component(t, l, n)
	
def component(t, l, n):
	t.fd(l)
	t.lt(180 - ((180 - 360 / n) / 2))
	t.fd(2 * l * math.sin((360 / n) / 180 * math.pi / 2))
	t.pu()        # 提笔
	t.home()      # 如果此时笔处于放下状态,相当于从当时所处位置对准原点画到原点的线段并把笔转向初始方向
	t.pd()        # 放笔
	
pie_chart(bob, 100, 20)

bob.ht()

turtle.mainloop()

在这里插入图片描述

  • 结果分析:

    • 实现了可通过输入不同实参 l 设置饼图大小、输入实参 n 设置饼图分区数,比较通用
    • Turtle 对象的home() 方法:使小乌龟转向原点、到原点画线段,并转向初始方向,除非笔处于抬起状态
    • for i in range(): 语句中的 i 默认从 0 开始计数
  • 变形练习:(通过中心点移位,在不同位置画不同的饼图)

import math
import turtle

bob = turtle.Turtle()

def pie_chart(t, l, n):
	for i in range(n):
		t.lt(360 / n * i)
		component(t, l, n)
	
def component(t, l, n):
	x = t.xcor()      # 获取小乌龟初始的 x 坐标
	y = t.ycor()      # 获取小乌龟初始的 y 坐标
	t.fd(l)
	t.lt(180 - ((180 - 360 / n) / 2))
	t.fd(2 * l * math.sin((360 / n) /180 * math.pi / 2))
	t.pu()
	# 中心点不一定是原点,用下两句代替 home()方法
	t.goto(x, y)      # 使小乌龟回到中心点初始坐标
	t.seth(0)         # 使小乌龟转向向水平向右
	t.pd()
	
pie_chart(bob, 100, 6)

bob.pu()
bob.setx(-300)        # 中心点从原点左移300像素
bob.pd()
pie_chart(bob, 100, 5)

bob.pu()
bob.setx(300)         # 中心点从原点右移300像素
bob.pd()
pie_chart(bob, 100, 7)

bob.ht()

turtle.mainloop()

在这里插入图片描述

  • 结果分析:
    • home() 方法仅适合于小乌龟回到原点并转向初始方向,本变形练习中的饼图中心点不全是原点,所以原练习中所用方法 home() 不再适合在此使用;
    • Turtle 对象移动到指定坐标和转向指定角度的方法:
      • setx(100):移动到横向坐标100(像素)
      • sety(100):移动到纵向坐标100(像素)
      • goto(100, 100):移动到横向坐标100、纵向坐标100的点
      • seth(90):转向90度方向

习题4-4:用函数绘制字母

【习题】 字母表中的字母可以由少量基本元素构成,例如竖线和横线,以及一些曲线。 设计一种可用由最少的基本元素绘制出的字母表,然后编写能画出各个字母的函数,为每个字母写一个函数,起名为draw_a,draw_b等等, 然后将函数放在一个名为 letters.py 的文件里

  • 练习记录:
import turtle

# 从自定义模块 polygon 中 import circle,arc等函数
from polygon import circle, arc

# 0 级基本体
# fd, bk, lt, rt, pu, pd

def fd(t, length):
    t.fd(length)

def bk(t, length):
    t.bk(length)

def lt(t, angle=90):
    t.lt(angle)

def rt(t, angle=90):
    t.rt(angle)

def pd(t):
    t.pd()

def pu(t):
    t.pu()


# 1 级基本体:0 级基本体的简单组合
def fdlt(t, n, angle=90):
    fd(t, n)
    lt(t, angle)

def fdbk(t, n):
    fd(t, n)
    bk(t, n)

def skip(t, n):
    pu(t)
    fd(t, n)
    pd(t)

def stump(t, n, angle=90):
    lt(t)
    fd(t, n)
    rt(t, angle)

def hollow(t, n):
    lt(t)
    skip(t, n)
    rt(t)


# 2 级基本体
def post(t, n):
    lt(t)
    fdbk(t, n)
    rt(t)

def beam(t, n, height):
    hollow(t, n*height)
    fdbk(t, n)
    hollow(t, -n*height)

def hangman(t, n, height):
    stump(t, n * height)
    fdbk(t, n)
    lt(t)
    bk(t, n*height)
    rt(t)

def diagonal(t, x, y):
    from math import atan2, sqrt, pi
    angle = atan2(y, x) * 180 / pi
    dist = sqrt(x**2 + y**2)
    lt(t, angle)
    fdbk(t, dist)
    rt(t, angle)

def vshape(t, n, height):
    diagonal(t, -n/2, height*n)
    diagonal(t, n/2, height*n)

def bump(t, n, height):
    stump(t, n*height)
    arc(t, n/2.0, 180)
    lt(t)
    fdlt(t, n*height+n)


"""
字母的绘制功能都有前提条件,乌龟在字母的左下角,后置条件是海龟在右下角,面对它开始的方向;它们都以Turtle 对象作为第一个参数,以 size 作为第二个参数;大多数字母宽 n 个像素,高 2n 个像素
"""

def draw_a(t, n):
    diagonal(t, n/2, 2*n)
    beam(t, n, 1)
    skip(t, n)
    diagonal(t, -n/2, 2*n)

def draw_b(t, n):
    bump(t, n, 1)
    bump(t, n, 0)
    skip(t, n/2)

def draw_c(t, n):
    hangman(t, n, 2)
    fd(t, n)

def draw_d(t, n):
    bump(t, 2*n, 0)
    skip(t, n)

def draw_ef(t, n):
    hangman(t, n, 2)
    hangman(t, n, 1)

def draw_e(t, n):
    draw_ef(t, n)
    fd(t, n)

def draw_f(t, n):
    draw_ef(t, n)
    skip(t, n)

def draw_g(t, n):
    hangman(t, n, 2)
    fd(t, n/2)
    beam(t, n/2, 2)
    fd(t, n/2)
    post(t, n)

def draw_h(t, n):
    post(t, 2*n)
    hangman(t, n, 1)
    skip(t, n)
    post(t, 2*n)

def draw_i(t, n):
    beam(t, n, 2)
    fd(t, n/2)
    post(t, 2*n)
    fd(t, n/2)

def draw_j(t, n):
    beam(t, n, 2)
    arc(t, n/2, 90)
    fd(t, 3*n/2)
    skip(t, -2*n)
    rt(t)
    skip(t, n/2)

def draw_k(t, n):
    post(t, 2*n)
    stump(t, n, 180)
    vshape(t, 2*n, 0.5)
    fdlt(t, n)
    skip(t, n)

def draw_l(t, n):
    post(t, 2*n)
    fd(t, n)

def draw_n(t, n):
    post(t, 2*n)
    skip(t, n)
    diagonal(t, -n, 2*n)
    post(t, 2*n)

def draw_m(t, n):
    post(t, 2*n)
    draw_v(t, n)
    post(t, 2*n)

def draw_o(t, n):
    skip(t, n)
    circle(t, n)
    skip(t, n)

def draw_p(t, n):
    bump(t, n, 1)
    skip(t, n/2)

def draw_q(t, n):
    draw_o(t, n)
    diagonal(t, -n/2, n)

def draw_r(t, n):
    draw_p(t, n)
    diagonal(t, -n/2, n)

def draw_s(t, n):
    fd(t, n/2)
    arc(t, n/2, 180)
    arc(t, n/2, -180)
    fdlt(t, n/2, -90)
    skip(t, 2*n)
    lt(t)

def draw_t(t, n):
    beam(t, n, 2)
    skip(t, n/2)
    post(t, 2*n)
    skip(t, n/2)

def draw_u(t, n):
    post(t, 2*n)
    fd(t, n)
    post(t, 2*n)

def draw_v(t, n):
    skip(t, n/2)
    vshape(t, n, 2)
    skip(t, n/2)

def draw_w(t, n):
    draw_v(t, n)
    draw_v(t, n)

def draw_x(t, n):
    diagonal(t, n, 2*n)
    skip(t, n)
    diagonal(t, -n, 2*n)

def draw_v(t, n):
    skip(t, n/2)
    diagonal(t, -n/2, 2*n)
    diagonal(t, n/2, 2*n)
    skip(t, n/2)

def draw_y(t, n):
    skip(t, n/2)
    stump(t, n)
    vshape(t, n, 1)
    rt(t)
    fdlt(t, n)
    skip(t, n/2)

def draw_z(t, n):
    beam(t, n, 2)
    diagonal(t, n, 2*n)
    fd(t, n)

def draw_(t, n):
    skip(t, n)

# 调用函数绘制汉语拼音 NIHAO
size = 20
bob = turtle.Turtle()
bob.pu()
bob.bk(100)
bob.pd()
draw_n(bob, size)   # 画 N
skip(bob, size)
draw_i(bob, size)   # 画 I
skip(bob, size)
draw_h(bob, size)   # 画 H
skip(bob, size)
draw_a(bob, size)   # 画 A
skip(bob, size)
draw_o(bob, size)   # 画 O
skip(bob, size)

turtle.mainloop()

在这里插入图片描述

  • 结果分析:
    • 分级抽象归纳出基本体,并编写出自定义函数,供上一级调用,到画字母共分了4级
    • 在画 O 和 Q 的函数 draw_o 、draw_q 中调用了 circle 函数、 在画 S 的函数 draw_s 中调用了 arc 函数,这两个函数都在自定义模块 polygon 中,该模块文件 polygon.py(提取码:9588) 必须与 letters.py 放在同一个文件夹中,circle 、 arc 才能在 letters.py 中被 import

习题4-5:用函数绘制螺线

【习题】 阅读螺线(spiral)的相关知识; 然后编写一个绘制阿基米德螺线(或者其他种类的螺线)的程序

  • 练习记录:
import math
import turtle

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

# 画直角坐标线
alice.bk(200)
alice.fd(400)
sam.lt(90)
sam.bk(200)
sam.fd(400)

# 定义画阿基米德螺线函数 Achimedean_spiral
def Achimedean_spiral(t, a, b):
	"""
	阿基米德螺线公式:r = a + θ*b
	"""
	r1 = a	                                 # 初始点与中心点距离 r1 
	theta = 0                              # 螺线转动到的角度,初值赋为 0,改变此初值可改变螺线偏转角度
	# 小乌龟移到第一笔起始点
	t.pu()
	t.goto(a * math.cos(theta / 180 * math.pi), a * math.sin(theta /  180 * math.pi))
	t.pd()
	alfa = 3                         # 每一笔螺线转3度,改变此值可改变精度
	for i in range(240):                     # 画240笔
		theta = theta + alfa                    
		r2 = r1 + (alfa / 180 * math.pi) * b    # 每一笔终点与中心点距离 r2 
		# 计算每一笔终点直角坐标(x, y)
		x = r2 * math.cos(theta / 180 * math.pi)
		y = r2 * math.sin(theta / 180 * math.pi)
		t.goto(x, y)                        # (x, y)为每一笔终点直角坐标
		t.seth(theta + 90)                  # 使笔与终点到中心点连线垂直
		r1 = r2                             # 把每一笔终点与中心点距离 r2 赋值给下一笔起始点与中心点距离 r1
	
# 调用画阿基米德螺线函数 Achimedean_spiral
Achimedean_spiral(bob, 0, 10)

turtle.mainloop()

在这里插入图片描述

  • 结果分析:
    • 画阿基米德螺线函数 Achimedean_spiral(用极坐标转换直角坐标的方法画阿基米德螺线) :把每一笔终点极坐标(r2, theta)转换成直角坐标 (x, y) ,再用 goto(x, y) 方法画起点到终点的线段,如下图:

      在这里插入图片描述

    • Achimedean_spiral函数定义时:

      • 改变 theta 初始值,可改变螺线偏转方向
      • 改变 n 值,可改变螺线长度
      • 改变 alfa 值,可改变螺线精度
    • Turtle 对象的 goto() 方法:定位并前往

    • 练习中,输入的实参 a 即起始点与中心点距离为 0 ,所画的螺线是中心点与起始点重合的特例,也可以输入其他大于零的数

  • 变形练习:(定义一个函数,用类似教材中画多边形的函数 polygon 中的方法画阿基米德螺线)
import math
import turtle

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

# 画直角坐标线
alice.bk(200)
alice.fd(400)
sam.lt(90)
sam.bk(200)
sam.fd(400)

# 定义画阿基米德螺线函数 Achimedean_spiral
def Achimedean_spiral(t, a, b):
	"""
	阿基米德螺旋线公式:r = a + θ*b
	"""
	r1 = a	                                     # 初始点与中心点距离 r1 
	theta = 60                                   # 螺线转动到的角度,初值赋为 60,改变此初值可改变螺线偏转角度
	alfa = 3                                # 每一笔螺线转3度,改变此值可改变精度
	# 小乌龟移到第一笔起始点
	t.pu()
	t.goto(a * math.cos(theta /180 * math.pi), a * math.sin(theta / 180 * math.pi))
	t.pd()
	
	for i in range(240):                        # 画240笔
		theta = theta + alfa                    
		r2 = r1 + (alfa / 180 * math.pi) * b    # 每一笔终点与中心点距离 r2 
		# 计算每一笔终点直角坐标 (x, y)
		x = r2 * math.cos(theta / 180 * math.pi)
		y = r2 * math.sin(theta / 180 * math.pi)
		t.goto(x, y)                            # (x, y)为每一笔终点直角坐标
		t.seth(theta + 90)                      # 使笔与终点到中心点连线垂直,与下一笔方向并不完全一致
		r1 = r2                                 # 把每一笔终点与中心点距离 r2 赋值给下一笔起始点与中心点距离 r1

# 定义画阿基米德螺线函数 Achimedean_spiral_2
def Achimedean_spiral_2(t, a, b):
	"""
	阿基米德螺旋线公式:r = a + θ*b
	"""
	r1 = a	                                    # 初始点与中心点距离 r1 
	theta = 60 	# 螺线转动到的角度,初值赋为60,改变此初值可改变螺线偏转角度
	alfa = 3	# 设定每一笔螺线转3度,改变此值可改变螺线精度
	# 小乌龟移到第一笔起始点
	t.pu()
	t.goto(a * math.cos(theta /180 * math.pi) , a * math.sin(theta / 180 * math.pi))
	t.pd()
	for i in range(180):                         # 画180笔	
		r2 = r1 + (alfa / 180 * math.pi) * b
		l = math.sqrt(r1*r1 + r2*r2 - 2 * math.cos(alfa / 180 * math.pi) * r1 * r2)
		if r1 == 0:
			orientation = theta + alfa             # 计算 a=0 时第一笔的朝向
		else:
			beta = math.acos((r1**2 + l**2 - r2**2) / (2 * r1 * l)) / math.pi * 180    # 根据余弦定理计算 beta( beta 见习题结果分析) 
			orientation = theta + 180 - beta	
		t.seth(orientation)
		t.fd(l)
		theta = theta + alfa
		r1 = r2
	
# 调用画阿基米德螺线函数 Achimedean_spiral
bob.pencolor(0,0,1)
Achimedean_spiral(bob, 0, 10)

# 调用画阿基米德螺线函数 Achimedean_spiral_2
tom.pencolor(1,0,0)
Achimedean_spiral_2(tom, 0, 10)

turtle.mainloop()

在这里插入图片描述

  • 结果分析:

    • 画阿基米德螺线函数 Achimedean_spiral_2(用类似教材中画多边形的函数 polygon 中的方法画阿基米德螺线) :用余弦定理计算画每一笔线段的长度 l 和用余弦定理计算角 beta 值及每一笔线段的朝向角 orientation ,再用 seth(orientation)fd(l) 方法从起点画线段,如下图:
      在这里插入图片描述

    • 调用函数 Achimedean_spiral_2 与 调用函数 Achimedean_spiral 所画的(分别为红色和蓝色)的螺线完全重合,说明只要方法正确,计算机都会给你正确结果

    • 但用余弦定理计算 beta 及 orientation 时,有个特例会出现报错,如下图:

      在这里插入图片描述

      从终端信息“ZeroDivisionError: float division by zero”可以看出,在运行到计算 beta 值的语句时出现了浮点数除以0的错误;分析该语句中的除法运算,显然是 r1 出现了为0的情况,而 r1 为0的情况只有在起始点与中心点重合(即 a 为0)且画第一笔时才会出现;定义函数 Achimedean_spiral_2 时须对 r1 为0的情况作例外处理以避免出现上述错误

    • 对边界数据的复核检验、边界情况的例外处理,是编程人员应有的思维方式,是编程过程中的例行事务


《Think Python 2e》作业实现(五): 条件和递归

习题5-1:转化格林威治时间

请写一个脚本读取当前格林威治标准时间,并且将其转换为纪元以来经过了多少天、小时、分钟和秒

  • 练习记录:
import time

s = time.time()

day = s // (60 * 60 * 24)
hour = (s - day * (60 * 60 * 24)) // (60 * 60)
minute = (s - day * (60 * 60 * 24) - hour * (60 * 60)) // 60
second = s - day * (60 * 60 * 24) - hour * (60 * 60) - minute * 60

print('time_now  ',s)
print('days      ',day)
print('hours     ',hour)
print('minutes   ',minute)
print('seconds   ',second)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\5_1.py
time_now   1609513414.5685637
days       18628.0
hours      15.0
minutes    3.0
seconds    34.56856369972229

习题5-2:检验费马大定理

【习题5.2.1】 写一个名为check_fermat的函数,接受四个形参——a,b,c以及n ——检查费马大定理是否成立。 如果 n 大于2且等式 an + bn = cn 成立,程序应输出“Holy smokes, Fermat was wrong!”, 否则程序应输出“No, that doesn’t work.”

  • 练习记录:
def check_fermat(a,b,c,n):
    if n > 2 and a**n + b**n == c**n:
        print('Holy smokes,Fermat was wrong!')
    else:
        print("No, that doesn't work.")

check_fermat(200, 300, 400, 3)
No, that doesnt work.
  • 结果分析:
    • 单引号或者双引号都可以用于表示字符串,但字符串里面有字符'的必须用双引号,如语句print("No,that doesn't work!")中,用单引号表示字符串就会出错
    Traceback (most recent call last):
     File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", 	line 31, in <module>
    	start(fakepyfile,mainpyfile)
    File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", 	line 30, in start
    	exec(open(mainpyfile).read(),  __main__.__dict__)
    File "<string>", line 5
    	print('No, that doesn't work.')
                          ^
    SyntaxError: invalid syntax
    

【习题5.2.2】 写一个函数提示用户输入a,b,c以及n的值,将它们转换成整型数, 然后使用check_fermat检查他们是否会违反了费马大定理

  • 练习记录:
def check_fermat(a,b,c,n):
    if n > 2 and a**n + b**n == c**n:
        print('Holy smokes,Fermat was wrong!')
    else:
        print("No, that doesn't work.")
         
prompt = 'Please enter the value of a.\n'
a = int(input(prompt))
prompt = 'Please enter the value of b.\n'
b = int(input(prompt))
prompt = 'Please enter the value of c.\n'
c = int(input(prompt))
prompt = 'Please enter the value of n.\n'
n = int(input(prompt))

check_fermat(a, b, c, n)
Please enter the value of a.
32
Please enter the value of b.
78
Please enter the value of c.
99
Please enter the value of n.
4
No, that doesn't work.
  • 结果分析:
    • 内置函数 input 输入的实参 prompt 等会作为提示信息会被显示;
    • 内置函数 input 接受见键盘输入后,返回的值是字符串,如果要得到一个整数或者实数,需要用 int 、float 等函数转换;
    >>> a = input('Enter a value:\n')
    Enter a value:
    32.8
    >>> a
    '32.8'
    >>> float(a)
    32.8
    

习题5-3:三根棒能搭三角形?

【习题5.3.1】 一个名为is_triangle的函数,其接受三个整数作为形参, 能够根据给定的三个长度的棒能否构成三角形来打印“Yes”或“No”

  • 练习记录:
def is_triangle(a, b, c):
	if a + b > c and a + c > b and b + c > a:
		print('Yes')
	else:
		print('No')
		
is_triangle(3, 4, 5)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new11.py
Yes

【习题5.3.2】 写一个函数,提示用户输入三根棍子的长度,将它们转换成整型数,然后使用 is_triangle检查给定长度的棒能否构成三角形

  • 练习记录:
def is_triangle(a, b, c):
	if a + b > c and a + c > b and b + c > a:
		print('Yes')
	else:
		print('No')

prompt = 'Enter the length of the first stick:   '
A = int(input(prompt))
prompt = 'Enter the length of the second stick:  '
B = int(input(prompt))
prompt = 'Enter the length of the third stick:   '
C = int(input(prompt))

is_triangle(A, B, C)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new12.py
Enter the length of the first stick:   1
Enter the length of the second stick:  2
Enter the length of the third stick:   3
No

习题5-4:递归函数的输出

def recurse(n, s):
    if n == 0:
        print(s)
    else:
        recurse(n-1, n+s)

recurse(3, 0)

【习题 5.4.1】 如果你这样调用函数: recurse(-1,0) ,会有什么结果?

  • 练习记录:
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new13.py
Traceback (most recent call last):
  File "D:\WorkSpace\thinkpython2e\new13.py", line 7, in <module>
    recurse(-1, 0)
  File "D:\WorkSpace\thinkpython2e\new13.py", line 5, in recurse
    recurse(n-1, n+s)
  File "D:\WorkSpace\thinkpython2e\new13.py", line 5, in recurse
    recurse(n-1, n+s)
  File "D:\WorkSpace\thinkpython2e\new13.py", line 5, in recurse
    recurse(n-1, n+s)
  [Previous line repeated 995 more times]
  File "D:\WorkSpace\thinkpython2e\new13.py", line 2, in recurse
    if n == 0:
RecursionError: maximum recursion depth exceeded in comparison
  • 结果分析:这样调用函数recurse(-1,0),递归永远不会达到基础情形,所以进入无限递归,直到达到 Python 的最大递归深度

【习题 5.4.2】 请写一个文档字符串,解释调用该函数时需要了解的全部信息(仅此而已)

  • 练习记录:
def recurse(n, s):
	"""  n>=0
	"""
    if n == 0:
        print(s)
    else:
        recurse(n-1, n+s)

  • 结果分析:
    • 为了不进入无限递归,对输入的实参 n 必须大于或者等于0的要求,用文档字符串形式予以说明

习题5-5:小乌龟画的树枝图

【习题】 阅读如下的函数,看看你能否看懂它是做什么的

  • 练习记录:
import turtle
tom = turtle.Turtle()

def fd(t, length):
	t.fd(length)
	
def bk(t, length):
	t.bk(length)
	
def lt(t, angle):
	t.lt(angle)
	
def rt(t, angle):
	t.rt(angle)

def draw(t, length, n):
	print("调用层数:", m + 1 - n)
	if n == 0:
		print("回到层数:", m - n)
		return
	angle = 50
	fd(t, length*n)
	lt(t, angle)
	draw(t, length, n-1)
	rt(t, 2*angle)
	draw(t, length, n-1)
	lt(t, angle)
	bk(t, length*n)
	if m -n == 0:
		print("回到主程序")
	else:
		print("回到层数:", m - n)

t = tom
length = 20
n = 2
m = n
print("开始主程序")
draw(t, length, n)
turtle.mainloop()

在这里插入图片描述

  • 结果分析:
    • 在分析函数递归调用时,必须分析清楚是什么时候 “递” 的,也就是必须分析清楚调用该函数的上一个函数的各形参、变量的值以及调用的语句(在本题的函数中,有两个语句调用下一个函数)

    • “归”(return) ,必须返回调用该函数的上一个函数,且返回到发生调用的语句的下一句,形参、变量的值保持调用时的值

    • 画函数堆栈图是分析函数状态比较好的方式,本题的函数堆栈图(忽略函数 lt、rt、fd 和 bk,附加了程序递归调用行号 ):在这里插入图片描述

    • 在程序中增加打印语句,例如print("调用层数:", m + 1 - n),让程序运行时在命令行窗口显示相关信息(如下图),是追踪函数递归调用的好方法:

      在这里插入图片描述

  • 变形练习:(画四个或多个分岔的图形)
import turtle
tom = turtle.Turtle()

def fd(t, length):
	t.fd(length)
	
def bk(t, length):
	t.bk(length)
	
def lt(t, angle):
	t.lt(angle)
	
def rt(t, angle):
	t.rt(angle)

def draw(t, length, n):
	print("调用层数:", m + 1 - n)
	if n == 0:
		print("回到层数:", m - n)
		return
	angle = 70
	fd(t, length*n)
	lt(t, angle)
	draw(t, length, n-1)
	rt(t, 2/3*angle)
	draw(t, length, n-1)
	rt(t, 2/3*angle)
	draw(t, length, n-1)
	rt(t, 2/3*angle)
	draw(t, length, n-1)
	lt(t, angle)
	bk(t, length*n)
	if m - n == 0:
		print("回到主程序")
	else:
		print("回到层数:", m - n)

t = tom
length = 20
n = 3
m = n
print("开始主程序")
draw(t, length, n)
turtle.mainloop()

在这里插入图片描述

习题5-6:画科赫曲线和雪花

【习题5.6.1】 写一个名为 koch 的函数,接受一个海龟和一个长度作为形参,然后 使用海龟画一条给定长度的科赫曲线

  • 练习记录:
import turtle

bob = turtle.Turtle()

def koch(t,n):
	if n < 5:
		t.fd(n)
		return
	n = n/3
	koch(t, n)
	t.lt(60)
	koch(t, n)
	t.rt(120)
	koch(t, n)
	t.lt(60)
	koch(t, n)

bob.pu()
bob.goto(-150, 90)
bob.pd()
koch(bob, 300)

bob.ht()
		
turtle.mainloop()

在这里插入图片描述
【习题5.6.2】 写一个名为 snowflake 的函数,画出三条科赫曲线,构成雪花的轮廓

  • 练习记录:
import turtle

bob = turtle.Turtle()

def koch(t,n):
	if n < 5:
		t.fd(n)
		return
	n = n/3
	koch(t, n)
	t.lt(60)
	koch(t,n)
	t.rt(120)
	koch(t,n)
	t.lt(60)
	koch(t,n)
	
def snowflake(t, n):
	for i in range(3):
		koch(t, n)
		t.rt(120)

bob.pu()
bob.goto(-150, 90)
bob.pd()
snowflake(bob, 300)

bob.ht()
		
turtle.mainloop()

在这里插入图片描述


《Think Python 2e》作业实现(六): 有返回值的函数

习题6-1:画组合函数的堆栈图

【习题】 画出下面程序的堆栈图,这个程序的最终输出是什么?

def b(z):
    prod = a(z, z)
    print(z, prod)
    return prod

def a(x, y):
    x = x + 1
    return x * y

def c(x, y, z):
	total = x + y + z
    square = b(total)**2
    return square

x = 1
y = x + 1
print(c(x, y+3, x+y))
  • 练习记录:
    在这里插入图片描述
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new27.py
9 90
8100

习题6-2:递归定义函数Ackermann

【习题】 编写一个叫作 ack 的函数来计算 Ackermann 函数,使用你的函数计算 ack(3,4),其结果应该为 125, 如果 m 和 n 的值较大时,会发生什么?

  • 练习记录:
def ack(m, n):
	if m == 0:
		return n + 1
	elif m > 0 and n == 0:
		return ack(m - 1, 1)
	elif m > 0 and n > 0:
		return ack(m - 1, ack(m, n - 1))
		
print(ack(3, 4))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new28.py
125
def ack(m, n):
	if m == 0:
		return n + 1
	elif m > 0 and n == 0:
		return ack(m - 1, 1)
	elif m > 0 and n > 0:
		return ack(m - 1, ack(m, n - 1))
		
print(ack(9, 10))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new28.py
Traceback (most recent call last):
  File "D:\WorkSpace\thinkpython2e\new28.py", line 11, in <module>
    print(ack(9, 10))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  [Previous line repeated 7 more times]
  File "D:\WorkSpace\thinkpython2e\new28.py", line 5, in ack
    return ack(m - 1, 1)
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 5, in ack
    return ack(m - 1, 1)
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 5, in ack
    return ack(m - 1, 1)
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 5, in ack
    return ack(m - 1, 1)
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 5, in ack
    return ack(m - 1, 1)
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  File "D:\WorkSpace\thinkpython2e\new28.py", line 7, in ack
    return ack(m - 1, ack(m, n - 1))
  [Previous line repeated 975 more times]
  File "D:\WorkSpace\thinkpython2e\new28.py", line 5, in ack
    return ack(m - 1, 1)
  File "D:\WorkSpace\thinkpython2e\new28.py", line 2, in ack
    if m == 0:
RecursionError: maximum recursion depth exceeded in comparison
  • 结果分析:RecursionError: maximum recursion depth exceeded in comparison显示超过了默认递归深度,默认为1000

习题6-3:检查字符串是不是回文字

【习题6.3.1】 将 first、last 和 middle 三个函数录入到文件 palindrome.py 中并测试,当你用一个两个字母的字符串调用 middle 时会发生什么?一个字母的呢?空字符串呢?空字符串这样'' 表示,中间不含任何字母

  • 练习记录:
def first(word):
    return word[0]

def last(word):
	return word[-1]

def middle(word):
    return word[1:-1]
	
print(middle('wow'))
print(middle('wo'))
print(middle('w'))
print(middle(''))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\palindrome.py
o


【习题6.3.2】 编写一个叫 is_palindrome 的函数,接受一个字符串作为实参。如果是回文词,就返回 True ,反之则返回 False,记住,你可以使用内建函数 len 来检查字符串的长度

  • 练习记录:
def first(word):
    return word[0]

def last(word):
	return word[-1]

def middle(word):
    return word[1:-1]
	
def is_palindrome(word):
	"""Returns True if word is a palindrome."""
	if len(word) <= 1:
		return True
	else:
		if first(word) == last(word):
			return is_palindrome(middle(word))
		else:
			return False
			
print(is_palindrome('w'))
print(is_palindrome('wo'))
print(is_palindrome('wow'))
print(is_palindrome('wowo'))
print(is_palindrome('wowow'))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\is_palindrome.py
True
False
True
False
True

习题6-4:检查两个数是不是幂的关系

【习题】 编写一个叫 is_power 的函数,接受两个参数 a 和 b, 并且当 a 是 b 的幂时返回 True

  • 练习记录:
def is_power(a, b):
	if a == 0 or b == 0 or a != int(a) or b != int(b):
		return 'The value given is not a nonzero integer.'
	if (a == 1) or (a == -1 and b == -1):
		return True
	if a/b == int(a/b) and abs(b) != 1:
		return is_power(a/b, b)
	return False

print(is_power(9, 0))
print(is_power(1.69, 1.3))
print(is_power(9, -3))
print(is_power(1, 1))
print(is_power(-1, 1))
print(is_power(1, -1))
print(is_power(-1, -1))
print(is_power(1, 3))
print(is_power(3, 1))
print(is_power(3, -1))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\is_power.py
The value given is not a nonzero integer.
The value given is not a nonzero integer.
True
True
False
True
True
True
False
False
  • 结果分析:1.69/1.3 = ? 这可真是个问题,在数学中精确答案是1.3,所以1.69是1.3的幂;但在 python 中计算时,由于精度所限为1.2999999999999998,如下图,1.69就不是1.3的幂;所以这个函数能接受的数值只能简化为整数
    在这里插入图片描述

习题6-6:求两个数的最大公约数

【习题】 求两个数的最大公约数的一种方法,是基于这样一个原理:如果 r 是 a 被 b 除后的余数,那么 gcd(a,b) = gcd(b, r) ,我们可以把 gcd(a, 0) = a 当做基础情形;编写一个叫 gcd 的函数,接受两个参数 a 和 b,并返回二者的最大公约数

  • 练习记录:
def gcd(a, b):
	if b == 0:
		return a
	return gcd(b, a%b)
	
print(gcd(111, 74))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\gcd.py
37

《Think Python 2e》作业实现(七): 迭代

习题7-1:复核牛顿法计算的平方根

【习题】 复制平方根一节中的循环,将其封装进一个叫 mysqrt 的函数中,这个函数接受 a 作为形参,选择一个合适的 x 值,并返回 a 的平方根估算值,为测试上面的函数,编写一个名为 test_squre_root 的函数,打印出如下表格:

a   mysqrt(a)     math.sqrt(a)  diff
-   ---------     ------------  ----
1.0 1.0           1.0           0.0
2.0 1.41421356237 1.41421356237 2.22044604925e-16
3.0 1.73205080757 1.73205080757 0.0
4.0 2.0           2.0           0.0
5.0 2.2360679775  2.2360679775  0.0
6.0 2.44948974278 2.44948974278 0.0
7.0 2.64575131106 2.64575131106 0.0
8.0 2.82842712475 2.82842712475 4.4408920985e-16
9.0 3.0           3.0           0.0
  • 练习记录:
import math

def mysqrt(a):
	x = a/2
	while True:
		y = (x + a/x) / 2
		if abs(y-x) < 0.0000000000000000000000001:
			break
		x = y
	return x
	
def test_squre_root(a):
	nat = "{:<4}\t{:<16}\t{:<16}\t{:<16}"
	mat = "{:.1f}\t{:<16}\t{:<16}\t{:<16}"
	print(nat.format('a', 'mysqrt(a)', 'math.sqrt(a)', 'diff'))
	print(nat.format('-', '---------', '------------', '----'))
	for i in range(a):
		my_sqrt = mysqrt(i+1)
		math_sqrt = math.sqrt(i+1)
		diff = abs(my_sqrt - math_sqrt)
		print(mat.format(i+1, my_sqrt, math_sqrt, diff))
 
test_squre_root(9)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\test_squre_root.py
a       mysqrt(a)               math.sqrt(a)            diff
-       ---------               ------------            ----
1.0     1.0                     1.0                     0.0
2.0     1.414213562373095       1.4142135623730951      2.220446049250313e-16
3.0     1.7320508075688772      1.7320508075688772      0.0
4.0     2.0                     2.0                     0.0
5.0     2.23606797749979        2.23606797749979        0.0
6.0     2.449489742783178       2.449489742783178       0.0
7.0     2.6457513110645907      2.6457513110645907      0.0
8.0     2.82842712474619        2.8284271247461903      4.440892098500626e-16
9.0     3.0                     3.0                     0.0
  • 结果分析:
    • 格式化函数 format 的应用参见 Python format 格式化函数 ,只用 print 函数无法实现习题要求的列宽度固定的输出
    • x = a/2,这行代码的作用是把牛顿法求平方根的 x 初始值设为 a 的一半,在这个习题中应该合适,哪怕直接把 x 值设为 a 也无妨
    • 再次强调 ,循环语句for i in range(a):中 i 是从0开始计数的,即 i = 0, 1, 2, …, (a-1),循环次数为 a

习题7-2 :计算 input 的算式的值

【习题】 编写一个名为 eval_loop 的函数,迭代式地提示用户输入,获取输入的内容,并利用 eval 来计算其值,最后打印该值,该程序应持续运行,直到用户输入 ‘done’,然后返回它最后一次计算的表达式的值

  • 练习记录:
def eval_loop():
	e = None
	while True:
		text = input('Please enter a calculation:')
		if text == 'done':
			break
		e = eval(text)
		print(e)
	print('Done!')
	return e		
	
print(eval_loop())
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\eval.py
Please enter a calculation:done
Done!
None
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\eval.py
Please enter a calculation:3**5+2*8+9
268
Please enter a calculation:done
Done!
268
  • e = None语句的加入是为了避免首次即输入 ‘done’ 导致返回值 e 未赋值而出错

习题7-3 :计算 pi 近似值

【习题】 编写一个名为 estimate_pi 的函数,利用拉马努金公式来估算并返回 pi 的值,这个函数应该使用 while 循环来计算所有项的和,直到最后一项小于1e-15时终止循环,将该值与 math.pi 进行比较,检测是否准确

  • 练习记录:
import math

def factorial(k):
	f = 1
	i = 1
	while True: 
		f = f*i
		i = i + 1
		if i > k:
			break
	return f

def delta(k):
	d = (factorial(4*k)*(1103 + 26390*k)) / ((factorial(k))**4*396**(4*k))
	return d

def xigama():
	i = 0
	x = 0
	while True:
		if delta(i) < 1e-15:
			break
		x = x + delta(i)
		i = i + 1	
	return x
	
def estimate_pi():
	estimate_pi = 9801/(2*math.sqrt(2)*xigama())
	return estimate_pi

print('estimate_pi =',estimate_pi())
print('epsilon =', (math.pi-estimate_pi()))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\estimate_pi.py
estimate_pi = 3.141592653589793
epsilon = 0.0
  • 结果分析:自定义阶乘函数 factorial 可以用 math 模块中的函数 math.factorial 替代

《Think Python 2e》作业实现(八): 字符串

习题8-1:字符串 strip 和 replace 方法

【习题 】 点击如下链接,阅读字符串方法的文档 http://docs.python.org/3/library/stdtypes.html#string-methods ,为了确保理解他们是怎么工作的,可以尝试使用其中的一些方法,strip 和 replace 尤其有用

  • 练习记录:
>>> 'www.example.com'.strip('cmowz.')
'example'
>>> '   spacious   '.strip()
'spacious'
>>> 'www.example.com'.strip('cmowz.')
'example'
>>> 'banana'.replace('a', 'e', 2)
'benena'

习题8-2:字符串 count 方法

【习题 】 字符串方法 count 类似于之前循环和计数一节中的 counter ,阅读这个方法的文档,写一个计算 banana 中 a 的个数的方法调用

  • 练习记录:
>>> 'banana'.count('a')
3
>>> 'banana'.count('na')
2

习题8-3:生成倒序字符串检查回文字

【习题 】 一个字符串切片可以接受指定步长的第三个索引,也就是连续字符间空格的个数,步长为2,意味着每隔一个字符,步长为3,意味着每隔两个字符,以此类推,步长为-1就是从单词的尾部开始进行, 所以切片 [::-1] 生成一个倒序的字符串,利用这个惯用法(idiom),将习题6-3中 is_palindrome 函数改写为一行代码版

  • 练习记录:
def is_palindrome(word):
	if word[::-1] == word:
		return True
	return False
	
print(is_palindrome('wow'))
print(is_palindrome('wowo'))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\8_3.py
True
False

习题8-4:这些函数都做了什么

【习题 】 下面这些函数,都是用于检查一个字符串是否包含一些小写字母的,但是其中至少有一些是错误的函数,检查每个函数,描述这个函数实际上做了什么(假设形参是字符串)

  • 练习记录:
def any_lowercase1(s):
	"""返回的结果是字符串第一个字符小写与否
	"""
	for c in s:
		if c.islower():
			return True
		else:
			return False
	
def any_lowercase2(s):
	"""返回的结果是'c'这个字符小写与否
	"""
	for c in s:
		if 'c'.islower():
			return 'True'
		else:
			return 'False'

def any_lowercase3(s):
	"""返回的结果是字符串最后一个字符小写与否
	"""
	for c in s:
		flag = c.islower()
	return flag

def any_lowercase4(s):
	"""返回的结果是字符串有小写字符与否
	"""
	flag = False
	for c in s:
		flag = flag or c.islower()
	return flag

def any_lowercase5(s):
	"""返回的结果是字符串全部字符小写与否
	"""
	for c in s:
		if not c.islower():
			return False
	return True

习题8-5:使得字符串整体偏移

【习题 】 编写一个叫 rotate_word 的函数,接受一个字符串和一个整数作为形参,并返回原字符串按照给定整数量偏移后得到的一个新字符串

  • 练习记录:
def rotate_letter(letter, n):
    if letter.isupper():
        start = ord('A')
    elif letter.islower():
        start = ord('a')
    else:
        return letter

    c = ord(letter) - start
    i = (c + n) % 26 + start
    return chr(i)


def rotate_word(word, n):
    res = ''
    for letter in word:
        res += rotate_letter(letter, n)
    return res
    
print(rotate_word('cheer', 7))
print(rotate_word('melon', -10))
print(rotate_word('sleep', 9))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\rotate.py
jolly
cubed
bunny
  • 结果分析:

    • 内置函数 ord 可以将字符转化成数值代码,chr 可以将数值代码转化成字符,字母表的字母以字母表顺序编码
    >>> ord('a')
    97
    >>> chr(99)
    'c'
    
    • 字符串的 isupper(islower) 方法,如果字符串全部是大写字母(小写字母)返回 True,否则返回 False
    >>> 'word'.islower()
    True
    >>> 'worD'.isupper()
    False
    

猜你喜欢

转载自blog.csdn.net/weixin_41217917/article/details/112931512