2.2.4 定义函数

之前的章节中我们使用了一些Python已有的函数, 这些函数不一定有我们需要的功能,比如,一个画正多边形的函数 看起来会很好用,但是turtle库中并没有直接提供。 所以我们需要自己定义函数。

本节目标:

  • 掌握函数和匿名函数

  • 尝试以函数为参数和返回值的函数

  • 通过画图锻炼自己将问题抽象化的能力

Learning By Reading 难度:★ 重要性:★★★★★

Learning By Doing 难度:★ 重要性:★★★★★

  • 定义函数polygon(n, x),功能为画一个边长为x的正n边形,并用该函数

    画一个正36边形。

Learning By Doing 难度:★★ 重要性:★★★

  • 画一个长这样的房子。

函数中可以调用其他函数,也可以调用自己,即递归。

通过递归可以画出一些很炫的分形(Fractal)图案。

Learning By Doing 难度:★★★ 重要性:★★★

  • 这里有些分形图形的例子,demo中的fractal.py中也有几个

    函数。选择自己喜欢的图形,试着画出来。

  • 提示:画这样图形的要领是将复杂的图形分解为自身的重复,对自身的重复即可以用递归实现。

一个例子来说明如何设计函数完成分形图形以及函数的一些其他注意事项

我们来以这个为例子画一画~ 完整版密恐友好版

从右到左分别是越来越复杂的图形,最右边我们暂且称为是0级的图形,最简单,相信你很快就能画出来。

t1 = turtle.Turtle()
t1.rt(90)

for i in range(3):
    t1.fd(20)
    t1.lt(120)

到0级左边一个的1级图形,很容易看出,它是由三个0级图形拼接起来的。

还记得我们这一节叫什么吗?叫函数。 把多次调用的代码定义成一个函数,我们就可以多次调用了。

之前我们已经画出了0级图形,花了5行代码。 应该怎么定义这个函数呢?

先不急~

我们用比较弱智的方法把1级图形画出来先~

从左下角的三角开始,再到右下角的,最后上面的。

t1 = turtle.Turtle()
t1.rt(90)

for i in range(3):
    t1.fd(20)
    t1.lt(120)
t1.fd(20)
for i in range(3):
    t1.fd(20)
    t1.lt(120)
t1.lt(60)
t1.fd(20)
t1.lt(60)
for i in range(3):
    t1.fd(20)
    t1.lt(120)

如果代码理解起来比较困难,可以在纸上一句一句,一句一句用笔模拟小海龟是怎么走的。

现在我们发现,

for i in range(3):
    t1.fd(20)
    t1.lt(120)

这段代码出现了三次。

于是我们可以把它定义成函数。

def level_0():
    for i in range(3): # 需要注意的是,函数体中的代码不会在定义的过程中执行
        t1.fd(20)      # 只有在调用函数的时候才会执行
        t1.lt(120)

而画1级图形的代码变成了

t1 = turtle.Turtle()
t1.rt(90)

level_0()
t1.fd(20)
level_0()
t1.lt(60)
t1.fd(20)
t1.lt(60)
level_0()

同样地,我们再笨笨地画一下2级图形

t1 = turtle.Turtle()
t1.rt(90)

level_0()
t1.fd(20)
level_0()
t1.lt(60)
t1.fd(20)
t1.lt(60)
level_0()
t1.lt(60)
t1.fd(20)
t1.lt(60)
t1.fd(20)
t1.lt(120)

t1.fd(40)

level_0()
t1.fd(20)
level_0()
t1.lt(60)
t1.fd(20)
t1.lt(60)
level_0()
t1.lt(60)
t1.fd(20)
t1.lt(60)
t1.fd(20)
t1.lt(120)

t1.lt(60)
t1.fd(40)
t1.lt(60)

level_0()
t1.fd(20)
level_0()
t1.lt(60)
t1.fd(20)
t1.lt(60)
level_0()
t1.lt(60)
t1.fd(20)
t1.lt(60)
t1.fd(20)
t1.lt(120)

代码炒鸡冗长了呢~ 不过自己动手在纸上画一下还是非常重要的。

空行已经把代码分成了几段,聪明的你一定能够发现其中有三段是一样的。 于是,

def level_1():
    level_0()
    t1.fd(20)
    level_0()
    t1.lt(60)
    t1.fd(20)
    t1.lt(60)
    level_0()
    t1.lt(60)
    t1.fd(20)
    t1.lt(60)
    t1.fd(20)
    t1.lt(120)

代码就变成了

t1 = turtle.Turtle()
t1.rt(90)

level_1()
t1.fd(40)
level_1()
t1.lt(60)
t1.fd(40)
t1.lt(60)
level_1()

这是2级图形的代码。

我们将它与1级图形的代码比较一下

t1 = turtle.Turtle() | t1 = turtl1.Turtle()
t1.rt(90)            | t1.rt(90)
                     |
level_0()            | level_1()
t1.fd(20)            | t1.fd(40)
level_0()            | level_1()
t1.lt(60)            | t1.lt(60)
t1.fd(20)            | t1.fd(40)
t1.lt(60)            | t1.lt(60)
level_0()            | level_1()

它们只有两个地方不一样,level_0变成了level_1,另外一个是20变成了40

找规律,然后我们猜测,画3级图形只需要把level_1改成level_2,然后40改成80就好了。 但level_2又是什么呢?

请大家试着再用笨办法画出3级图形,然后得到level_2

def level_2():
    level_1()
    t1.fd(40)
    level_1()
    t1.lt(60)
    t1.fd(40)
    t1.lt(60)
    level_1()
    t1.lt(60)
    t1.fd(40)
    t1.lt(60)
    t1.fd(40)
    t1.lt(120)

它与level_1也只有几个数字的区别。

于是我们可以再定义一个函数level(n)level(1)等价于level_1()level(2)等价于level_2()

def level(n):
    level(n-1)
    t1.fd(10*(2**n))
    level(n-1)
    t1.lt(60)
    t1.fd(10*(2**n))
    t1.lt(60)
    level(n-1)
    t1.lt(60)
    t1.fd(10*(2**n))
    t1.lt(60)
    t1.fd(10*(2**n))
    t1.lt(120)

这样我们发现一个问题,level(2)调用level(1)level(1)调用level(0)level(0)调用level(-1),……

所以我们加上判断,

def level(n):
    if n == 0:
        for i in range(3):
            t1.fd(20)
            t1.lt(120)
        return
    level(n-1)
    t1.fd(10*(2**n))
    level(n-1)
    t1.lt(60)
    t1.fd(10*(2**n))
    t1.lt(60)
    level(n-1)
    t1.lt(60)
    t1.fd(10*(2**n))
    t1.lt(60)
    t1.fd(10*(2**n))
    t1.lt(120)

这样,画n级图形,我们可以用

t1 = turtle.Turtle()
t1.rt(90)

level(n)

将代码适当简化一下,变成

def level(n):
    if n:
        for i in range(3):
            level(n-1)
            t1.fd(20*(2**n))
            t1.lt(120)

我们再回过头来看图形本身。我们将n级图形分成了3个n-1级图形,画n级图形时,调用自身3次就可以了。 调用自身在编程中叫递归,这种将大问题分成若干相同或相似的小问题的方法叫分治,而我们从0级开始,一级一级往上总结出规律叫归纳

Tasks For Tutors

  • 关于递归,教我LOGO语言的老师曾引用一位活在传说中的独臂老师的方法告诉我们一个比较巧妙的办法来看出一段类似上面的分形图形代码画的是什么。

    比如上面的level,我们先把所有递归语句level(n-1)遮掉不许看,再忽略掉n==0的特殊处理,变成

    for i in range(3):
      遮掉不许看
      t1.fd(20*(2**n))
      t1.lt(120)

    这样画出来一个三角形。每个顶点都是一个“递归点”,在每个“递归点”同样地画三角形(即体现出递归是“调用自己”)。

    这位老师若是放在DND的阵营⑨宫格中大概会是绝对中立,他的这个方法隐约也透出同样的禅意——邪道但有用。

  • 根据自己丰富的编程经验说说自己会怎么处理分形。并选几个例子,组织学员们相互讲解如何将分形图形“分解”。

  • 画房子的例子里可能会用到开根号,简单介绍math库。

Learning By Doing 难度:★★ 重要性:★★

  • 这里有更多的画图练习题,

    可以选一些练练手,熟悉ifwhile语句,熟悉小海龟的基本操作,以及Python的其他特性。

最后提一句,函数的参数可以是任何类型的对象,一个函数也可以作为参数或者返回值。 这里先提一句,后面我们会碰到更多相关的例子。

Last updated