在本章中,我们将编写简短的、简单的程序来创建漂亮的、复杂的视觉效果。为了做到这一点,我们可以使用海龟作图软件。在海龟作图中,我们可以编写指令让一个虚拟的(想象中的)海龟在屏幕上来回移动。这个海龟带着一只钢笔,我们可以让海龟无论移动到哪都使用这只钢笔来绘制线条。通过编写代码,以各种很酷的模式移动海龟,我们可以绘制出令人惊奇的图片。
使用海龟作图,我们不仅能够只用几行代码就创建出令人印象深刻的视觉效果,而且还可以跟随海龟看看每行代码如何影响到它的移动。这能够帮助我们理解代码的逻辑。
第一个海龟程序
让我们使用海龟作图来编写第一个程序。在一个新的IDLE窗口中输入如下的代码并将其保存为SquareSpiral1.py序)。
SquareSpiral1.py
# SquareSpiral1.py - Draws a square spiral
import turtle
t = turtle.Pen()
for x in range(100):
t.forward(x)
t.left(90)
用简短的SquareSpiral1.py程序创建的一个炫目的正方形螺旋线
是如何工作的
让我们一行一行地分析这个程序,看看它是如何工作的。SquareSpiral1.py的第1行是注释。正如我们在第1章中所学过的,注释以一个井号(#)开头。注释允许我们在程序中写入给自己或以后可能阅读该程序的其他人一些提示。计算机不会阅读或试图理解井号之后的任何内容;注释只是让我们写出关于程序是做什么的一些说明。在这个例子中,我们将程序的名称以及针对其做什么的一个简单说明放入到注释之中。
第2行导入(import)了绘制海龟图形的功能。导入已经编写过的代码,这是编程工作的最酷的事情之一。如果我们编写了一些有趣并有用的程序,可以将其与其他的人分享,同时也可以自己重用它。尽管海龟作图最初源自20世纪60年代的Logo编程语言[1],但一些很酷的Python程序员构建了一个库(library,库就是可以重用的代码的一个集合),来帮助其他程序员在Python中使用海龟作图。当我们输入了import turtle,就表示我们的程序能够使用那些Python程序员所编写的代码。图2-1中的小的黑色箭头表示海龟,它在屏幕上移动的时候会使用钢笔绘图。
程序的第3行是t = turtle.Pen(),它告诉计算机,我们将使用字母t表示海龟的钢笔。这使得我们只需要录入t.forward(),而不是turtle.Pen().forward(),就可以让海龟在屏幕上移动的时候用海龟的钢笔进行绘制。字母t是告诉海龟做什么的一种快捷方式。
第4行最为复杂。在这里,我们创建了一个循环(loop),它重复一组指令很多次(一次又一次地循环这些代码行)。这个特定的循环设置了一个范围(range,或列表),其中拥有从0~99的100个数字(计算机几乎总是从0开始计数,而不是像我们通常那样从1开始)。在该循环中,字母x遍历了范围中的每一个数字。因此,x从0开始,然后变为1,然后是2,依次类推,直到99,一共100个步骤。
x叫作变量(variable)[2](在第1章中的YourName.py程序中,name就是变量)。变量存储了在程序进行的过程中可以修改(变化)的一个值。我们在所编写的几乎每一个程序中,都要使用变量,因此,早点认识变量为好。
接下来的两行代码缩进了,或者说,在左边留出了空格。这意味着,它们位于该循环之中(in the loop)并且和上面的那一行代码一起,每次x从0~99的范围中获取一个新的数字的时候,这些代码行都会重复,直到达到100次。
让我们看看Python初次读取这一组指令的时候发生了什么。命令t.forward(x)让海龟的钢笔在屏幕上向前移动x个点。因为x是0,钢笔根本不会移动。最后一行代码t.left(90)让海龟向左转90°,或者说转四分之一个圈。
由于这个for循环,程序继续运行并且回到了循环的开始位置。计算机加1后将x移动到范围中的下一个值,因为1仍然位于从0~99的范围中,循环继续。现在x是1,因此,钢笔向前移动1个点。然后,钢笔向左移动90个点,因为代码是t.left(90)。这样一次一次地继续执行,当x到达99,即循环的最后一次迭代,钢笔围绕着正方形螺旋线的外围画了一条长长的线条。
下面我们随着x从0增加到100,将循环的每一步可视化地表示出来。
- for x in range(100):
- t.forward(x)
- t.left(90)
循环0到4:绘制了前4条线(在x = 4之后)。
循环5到8:绘制了另外4条线;正方形出现了。
循环9到12:正方形螺旋线变为了12条线(3个正方形)。
计算机屏幕上的点或像素可能太小了,以至于我们无法很好地看到它们。但是,随着x变得越来越接近100,海龟绘制的线条包含了越来越多的像素。换句话说,当x变得越来越大,t.forward(x)绘制的线条越来越长。屏幕上的海龟箭头,绘制一会儿,然后向左转,再绘制一会儿,再向左转,这样一次又一次地绘制,每次线条都变得越来越长。
最后,我们有了一个炫目的正方形形状。连续4次向左转90°,就可以得到一个正方形,就像是围绕一栋建筑连续4次左转的话,将会带着我们绕建筑转一圈并且回到起点一样。
在这个示例中,我们之所以得到一个螺旋线,是因为每次左转的时候,都走得更远一点。绘制的第一个线条只是1步长(x = 1的时候),然后是2(循环的下一次迭代),然后是3,然后是4,以此类推,直到达到100步长,这时候,线条的长度为99像素。再一次强调下,屏幕上的像素可能太小了,以至于我们无法很容易地看到单个的点,但是,它们是存在的,而且我们会看到随着程序包含更多的像素,线条会变得越来越长。
通过完成所有的90°角的旋转,我们得到了完美的正方形。
旋转的海龟
让我们看看当修改了程序中某一个数值的时候,会发生什么?学习和程序相关的新知识的一种方法是,当我们修改其某一个部分的时候,看看发生了什么。我们不会总是得到一个很好的结果,但是,即使是某些地方出错的时候,我们也能学到东西。
我们只是将程序的最后一行修改为t.left(91),将其保存为SquareSpiral2.py。
SquareSpiral2.py
- import turtle
- t = turtle.Pen()
- for x in range(100):
- t.forward(x)
- t.left(91)
我们提到了向左转90°会创建一个完美的正方形。每次向左转的比90°多一点点的话(在这个例子中,是91°),会将正方形略微向外抛出一点点。由于我们进行下一次旋转的时候,已经偏离了一点点,随着程序继续进行,新的图形越来越不像是一个正方形。实际上,它创建了一个开始向左旋转的、漂亮的螺旋形,就像是楼梯一样,如图2-2所示。
图2-2 正方形螺旋线程序略作修改后变成了一个螺旋形的楼梯
这也是一个漂亮的图形,可以帮助我们理解如何只略微修改一个数字,就显著地改变程序的结果。1°似乎并不是一个很大的偏差,除非我们偏离1° 100次(这加起来就是100°),或者1000次,或者,如果我们使用的是飞机着陆程序……
如果还不知道度是如何工作的,现在先不要担心,我们只要尝试修改数字,看看发生了什么就好了。我们通过修改range后面的圆括号中的值,让程序绘制的线条数达到200或500,或者50。
我们再尝试将最后一行的角度修改为91、46、61或121等。记住每次都保存程序,然后,我们运行它,看看所做的修改会如何影响到程序的绘制。年龄大一点的读者了解一些几何知识,可能会根据不同的角度看到一些熟悉的形状,甚至能够在程序运行之前根据角度来预测出形状。较小的读者则只能够感受修改带来的变化,等他们某一天上了几何课之后,可以再回头来看这个练习。
海龟画圆
说到几何,海龟作图可以绘制很多有趣的形状,而不只是直线。我们将在2.4节中再次回到正方形,但现在,让我们来更多地了解一下Python Turtle库。
我们再来修改一行代码:t.forward(x)。我们在前面看到了这条命令或函数,它将海龟的钢笔向前移动x个像素并且绘制一条笔直的线段;然后,海龟转向并且再次绘制。如果我们修改这行代码来绘制更为复杂一点的图形,例如圆,那会怎么样呢?
好在,绘制一个固定大小(或半径)的圆的命令,和绘制一条直线的命令一样简单。我们将t.forward(x)修改为t.circle(x),如下面的代码所示。
CircleSpiral1.py
- import turtle
- t = turtle.Pen()
- for x in range(100):
- t.circle(x)
- t.left(91)
哦,将一条命令从t.forward修改为t.circle,会得到一个复杂得多的形状,如图2-3所示。t.circle(x)函数让程序在当前位置绘制了一个半径为x的圆。注意,这个绘制和简单的正方形螺旋线有一些相同点:它也有4组圆形的螺旋线,就像是正方形的螺旋线有4个边一样。这是因为我们使用t.left(91)命令,每次向左旋转都将超过90°一点点。如果我们学习过几何就知道,围绕一个点转一圈有360°,就像是一个正方形有4个90°的角(4×90 = 360)。海龟通过每次围绕图形旋转的比90°多一点点,从而绘制出这个螺旋线的形状。
图2-3 只需在改动一点就得到一组漂亮的4个螺旋线的圆
我们将会看到的一个区别是,圆形螺旋线比正方形螺旋线要大一些,实际上,大约是前者两倍那么大。这是因为t.circle(x)使用x作为圆的半径,而这是从圆心到边缘的距离,大概是圆的宽度的一半。
半径为x意味着,圆的直径,也就是说总的宽度是x的两倍。换句话说,t.circle(x)绘制的圆,当x等于1的时候,总宽度为2个像素;当x为2的时候总宽度为4个像素;按照这种方式,直到x等于99的时候,其宽度为198个像素。这几乎是200个像素宽了,或者说是正方形边最大的时候的两倍,因此,圆螺旋线看上去是正方形螺旋线的两倍的大小,当然,也会加倍的酷!
添加颜色
这些螺旋线的形状不错,但是,如果它们能够更多彩一些,是不是更酷呢?让我们回到正方形螺旋线代码,在t = turtle.Pen()这一行的后面再添加一行代码,从而将钢笔颜色设置为红色。
SquareSpiral3.py
- import turtle
- t = turtle.Pen()
- t.pencolor(“red”)
- for x in range(100):
- t.forward(x)
- t.left(91)
运行该程序,我们将会看到正方形螺旋线的一个更多色彩的版本,如图2-4所示。
图2-4 正方形螺旋线变得更多彩一些了
我们尝试用另一种常用的颜色(如“blue”或“green”)来替换掉“red”或“green”并且再次运行该程序。我们可以通过Turtle库使用数百种不同的颜色,包括一些奇怪的颜色,如“salmon”和“lemon chiffon”(访问http://www.tcl.tk/man/tcl8.4/TkCmd/colors.htm可以查看完整的列表)。让整个螺旋线呈现一种不同的颜色是很不错的一步,但是,如果想要让每一边都显示一种不同的颜色,我们该怎么办呢?这需要对程序做一些更多的修改。
一个四色螺旋线
让我们来考虑一下算法(algorithm)。算法就是一系列的步骤,它可以将单色的螺旋线变为4色的螺旋线。大多数的步骤和之前的螺旋线程序中相同,但是,这里还增加了一些调整:
(1)导入turtle模块并且设置一个海龟;
(2)告诉计算机应该使用何种颜色;
(3)设置一个循环,绘制螺旋线中的100条线段;
(4)为螺旋线的每一边选取一种不同的钢笔颜色;
(5)向前移动海龟以绘制每一边;
(6)将海龟向左转,以准备好绘制下一边。
首先,我们需要颜色名称的一个列表,而不是单个的颜色,因此,我们要创建一个名为colors的列表变量并且在列表中放置4种颜色,如下所示。
colors = [“red”, “yellow”, “blue”, “green”]
这个4种颜色的列表,将会针对正方形的每一边给出一种颜色。注意,我们将颜色的列表放在了方括号“[”和“]”之间。这里要确保引号中的每一种颜色名都像我们在第1章中打印出来的单词一样,因为这些颜色名都是字符串(string)或文本值,这是我们稍后要传递给pencolor函数的值。正如前面所提到的,我们使用一个名为colors的变量来存储4种颜色的列表。因此,任何时候,当想要从列表中获取颜色的时候,我们都要使用colors变量来表示钢笔的颜色。记住,变量存储的值是变化的,这正如同其名称一样,变量嘛。
我们需要做的下一件事情是,每次遍历绘制循环的时候修改钢笔颜色。为了做到这一点,我们需要将t.pencolor()函数移入到for循环下的一组指令之中,还需要告诉pencolor函数,我们想要使用列表中的哪一种颜色。
我们输入如下的代码并运行它。
ColorSquareSpiral.py
- import turtle
- t = turtle.Pen()
- colors = [“red”, “yellow”, “blue”, “green”]
- for x in range(100):
- t.pencolor(colors[x%4])
- t.forward(x)
- t.left(91)
4种颜色的列表起作用了,我们在这个运行的示例中看到了它们(如图2-5所示)。到目前为止,一切还不错。
图2-5 正方形螺旋线程序的一个更加多彩的版本
pencolor函数中唯一的新增部分是(colors[x%4])。这条语句中的x和我们在程序中其他地方所使用的x是同一个变量,因此,x将持续从0~99增加,就像我们前面所见到的那样。圆括号中的colors变量名告诉Python,从我们在程序前面所添加的、名为colors的颜色名称列表中选取一种颜色。
[x%4]告诉Python我们将使用colors列表中的前4种颜色,即编号从0~3的颜色并且每当x变化的时候就遍历它们。在这个例子中,我们的颜色列表只有4种颜色,因此,我们需要一次又一次地遍历这4种颜色。
- colors = [“red”, “yellow”, “blue”, “green”]
- 0 1 2 3
[x%4]中的“%”叫作模除操作符(modulo operator),表示一次除法运算中的余数(remainder)(5÷4商1余1,因此,5可以包含4一次并且还剩下1;6÷4余2,以此类推)。当我们想要遍历列表中一定数目的项时,例如我们对4种颜色列表所做的操作,模除操作符很有用。
在100步中,colors[x%4]将遍历4种颜色(0、1、2和3,分别表示红色、黄色、蓝色和绿色)整整25次。如果我们有时间(并且有一个放大镜),可以数一数图2-5中有25条红色的、25条黄色的、25条蓝色的和25条绿色的线段。第1次遍历绘制循环的时候,Python使用列表中的第一种颜色,红色;第2次遍历的时候,它使用黄色,以此类推。第15次遍历循环的时候,Python又回过头来使用红色,然后是黄色,等等;每通过循环4次之后,总是又回过头来使用红色。
修改背景颜色
让我们再次加入一点内容,创造出比图2-5更漂亮一些的内容。正如我5岁的儿子Alex所指出来的那样,黄色部分太难以识别出来了。这就像是在白色的绘画纸上使用黄色的蜡笔一样,屏幕上的黄色像素无法在白色背景上明显地显示出来。让我们把背景颜色修改为黑色,来修正这个问题。我们在程序中的import行之后的任何位置,输入如下的代码行。
turtle.bgcolor(“black”)
添加这一行之后,图片更加漂亮,所有的颜色现在都处在一个黑色的背景之上。注意,海龟钢笔(在程序中由变量t表示)没有任何变化。相反,我们修改了海龟屏幕的一些内容,也就是背景颜色。turtle.bgcolor()命令允许我们将整个绘制屏幕修改为Python中指定的任何颜色。在turtle.bgcolor(“black”)这一行中,我们选择了黑色作为屏幕颜色,因此,红色、黄色、蓝色和绿色都显示得很好。
此外,我们可以将循环中的range()修改为200甚至更大,以使得螺旋线中的正方形更大。在黑色背景上显示200个线段的新版本的图片,如图2-6
所示。
图2-6 螺旋线程序的路还很长(这是一个简单的开始)
Alex总是想帮助我的程序变得更为惊人,他要求再做一项修改:如果现在把线段替换为圆,那会怎么样呢?那会不会是最酷的图片呢?好吧,我必须承认,这甚至会更酷。完整的代码如下所示。
ColorCircleSpiral.py
- import turtle
- t = turtle.Pen()
- turtle.bgcolor(“black”)
- colors = [“red”, “yellow”, “blue”, “green”]
- for x in range(100):
- t.pencolor(colors[x%4])
- t.circle(x)
- t.left(91)
我们可以在图2-7中看到结果。
图2-7 Alex的惊人的圆螺旋线— 一共8行代码,简单而优雅
一个变量搞定一切
到目前为止,我们已经使用变量来修改颜色、大小以及螺旋线形状的旋转角度。让我们再添加一个sides变量,来表示形状的边数。这个新的变量如何改变我们的螺旋线呢?如果要搞清楚这一点,我们尝试这个新的程序ColorSpiral.py。
ColorSpiral.py
- import turtle
- t = turtle.Pen()
- turtle.bgcolor(“black”)
- # You can choose between 2 and 6 sides for some cool shapes!
- sides = 6
- colors = [“red”, “yellow”, “blue”, “orange”, “green”, “purple”]
- for x in range(360):
- t.pencolor(colors[x%sides])
- t.forward(x * 3/sides + x)
- t.left(360/sides + 1)
- t.width(x*sides/200)
我们可以将sides的值从6改为2(1个边并不是很有趣,也不能使用太大的数字,除非我们在程序的第6行中的列表中,添加更多的颜色),然后保存该程序并且可以运行任意多次。图2-8展示了用sides=6、sides=5,一直到sides=2所创建的图像,其中sides=2的图像很奇怪,这就是图2-8(e)所显示的扁平的螺旋线。我们可以改变列表中的颜色的顺序,也可以在绘制循环之中的任意函数中,使用较大一些或较小一点的数字。如果把程序给搞乱了,我们只需要返回到最初的ColorSpiral.py程序重新来玩就好了。
a)
b)
c)
d)
e)
图2-8 通过把变量sides从6(a)修改为2(e)所创建的5种彩色的形状
ColorSpiral.py程序使用了一条新的命令t.width(),它修改了海龟钢笔的宽度。在我们的程序中,随着钢笔绘制的形状越来越大,钢笔变得越来越宽(其线条变得更粗)。在第3章和第4章,我们学习创建程序所需的其他技能的时候,还会再次遇到这个程序以及其他类似的程序。
本章小结
在本章中,我们使用Turtle库的工具绘制了令人印象深刻的彩色形状。我们使用import命令把这个库导入到自己的程序中,同时了解到,以这种方式来重用代码是编程的最强大的功能之一。一旦编写了有用的内容,或者借用某些人慷慨分享的代码,我们不仅能够、节省时间,而且能够使用这些导入的代码做全新的事情。
我们还介绍了程序中像x和sides这样的变量。这些变量存储或记住一个数字或值,以便我们能够在程序中多次使用它,甚至修改其值。在第3章中,我们将学习变量的作用以及Python如何能够帮助你完成数学作业。
现在,我们应该能够做如下这些事情:
用Turtle库绘制简单的图形;
使用变量来存储简单的数值和字符串;
在IDLE中修改、保存和运行程序。
编程挑战
尝试这些挑战以练习我们在本章中所学习的知识(如果遇到困难,可以访问http://www.nostarch.com/teachkids/寻找示例解答)。
[1] Logo编程语言创建于1967年,这是一种教育编程语言,在50年之后的今天,它仍然用来教授基本的编程。这很酷,是不是?
[2] 小读者可能会把x当作未知数,就像当他们求解x + 4 = 6以求得未知的x一样。年龄大一点的读者可能会通过代数课或其他的数学课程认识x,早期的程序员正是从代数和数学中借用了变量的概念。编写代码的过程中会有很多数学的典型例子,我们甚至会在后面见到一些很酷的几何示例。