给普通人的Python——第五章

5. 代码重复使用的艺术:函数

编程中的函数和我们数学中的函数内涵是不同的。简单说,编程中的函数,其实就是一组代码。那为什么要把这些代码放到一起呢?用任何语言来表达这个问题都会显得抽象和难以理解。我们不如直接回到没有函数的情况下去写代码,亲自体会发明函数的必要性。

5.1 一个例子引入

我们上次学习了如何使用“海龟”先生画图,那我们这次再复习一下用法

我们今天就来画下面这幅图

给普通人的Python——第五章

在画这种图之前,我们肯定先得画一个草图,计算好所有的坐标位置,我们用红色虚线表示坐标轴,两条虚线交点就是原点,如下
给普通人的Python——第五章

大家可以先自己画一画,代码如下

import turtle

# 创建一个窗口对象,并赋值给变量window
window = turtle.Screen()

# 创建一个海龟对象,并赋值给变量tt
tt = turtle.Turtle()

# 抬起画笔
tt.penup()

# 将画笔移动到坐标为(150,150)的位置
tt.goto(150, 150)

# 落下画笔
tt.pendown()

# 右转90度
tt.right(90)

# 画外层的大长方形
tt.forward(450)
tt.right(90)
tt.forward(300)
tt.right(90)
tt.forward(450)
tt.right(90)
tt.forward(300)

# 画顶部的直角三角形
tt.left(135)
tt.forward(212)
tt.left(90)
tt.forward(212)

# 画中心的半圆形
tt.penup()
tt.goto(50, 0)
tt.pendown()
tt.left(225)
tt.circle(50, 180)

# 画正方形窗户,需要注意,画完圆形后,画笔朝向已经向下
tt.penup()
tt.goto(50, 0)
tt.pendown()

tt.forward(100)
tt.right(90)
tt.forward(100)
tt.right(90)
tt.forward(100)
tt.right(90)
tt.forward(100)

# 画窗户中的十字
tt.penup()
tt.home()
tt.pendown()
tt.right(90)
tt.forward(100)

tt.penup()
tt.goto(50, -50)
tt.pendown()
tt.right(90)
tt.forward(100)

# 隐藏画笔(小三角)
tt.hideturtle()
# 不让窗口立刻退出
window.mainloop()

上面的代码密密麻麻写了60多行,我们仔细观察会发现,其中有很多是重复的代码,比如下面这一段

tt.right(90)
tt.forward(100)
tt.right(90)
tt.forward(100)
tt.right(90)
tt.forward(100)

我们人类的进步是建立在偷懒的基础上的,那就会有人想了,既然是重复的代码,那我们程序员能不能偷一下懒,少写一点呢?这里的例子只有几行重复代码,那如果是一个复杂的程序,有几百行重复的代码呢?这偷懒可不是省下一点点工作量,那是节省了大量的工作量。

这个时候就可以用上我们开头说的函数了,将相关代码放到一起,形成一组代码。修改代码如下

import turtle

# 创建一个窗口对象,并赋值给变量window
window = turtle.Screen()

# 创建一个海龟对象,并赋值给变量tt
tt = turtle.Turtle()

# 抬起画笔
tt.penup()

# 将画笔移动到坐标为(150,150)的位置
tt.goto(150, 150)
# 落下画笔
tt.pendown()
# 右转90度
tt.right(90)

# 画外层的大长方形
tt.forward(450)
tt.right(90)
tt.forward(300)
tt.right(90)
tt.forward(450)
tt.right(90)
tt.forward(300)

# 画顶部的直角三角形
tt.left(135)
tt.forward(212)
tt.left(90)
tt.forward(212)

# 画中心的半圆形
tt.penup()
tt.goto(50, 0)
tt.pendown()
tt.left(225)
tt.circle(50, 180)

# 画正方形窗户,需要注意,画完圆形后,画笔朝向已经向下
tt.penup()
tt.goto(50, 0)
tt.pendown()

tt.forward(100)

# tt.right(90)
# tt.forward(100)
# tt.right(90)
# tt.forward(100)
# tt.right(90)
# tt.forward(100)

def repeat():
    tt.right(90)
    tt.forward(100)


# 调用repeat函数
repeat()
repeat()
repeat()

# 画窗户中的十字
tt.penup()
tt.home()
tt.pendown()
tt.right(90)
tt.forward(100)

tt.penup()
tt.goto(50, -50)
tt.pendown()
tt.right(90)
tt.forward(100)

# 隐藏画笔(小三角)
tt.hideturtle()
# 不让窗口立刻退出
window.mainloop()

如上,我们将重复的代码注释掉了(即不再生效),然后定义了一个函数叫repeat,这个函数块里面包含两句代码,这个函数就相当于是一个组,然后调用三次这个函数,就替代了之前被注释的那段代码。我们重新运行程序,功能和之前一模一样。

使用函数封装了一组代码,今后只需要调用这个函数就可以了,这里代码量少,看不出有什么优势,我们后面会接着封装。

5.2 定义函数

我们先学习一下函数的定义方式

给普通人的Python——第五章

def是单词define的缩写,定义的意思。要记住,Python中的所有缩进都是4个空格。这里我们没有定义参数,所以参数列表是空的。要注意,定了一个函数,这个函数里面的代码并不会执行,只有当调用函数时,函数里面的代码才会执行!调用函数就很简单了,我们已经调用过很多次函数了,只需要在函数名后面加一对因为括号就可以。如果这个函数有参数,那么需要在括号中传入参数。

先看一个简单示例

def add(x,y):
    print(x + y)

add(19,1)

输出结果:
20

我们定义了一个加法函数,在参数列表处定义了两个参数xy,这说明调用这个函数时,必须传入两个参数,否则会报错。定义完成后,我们去调用这个函数,并传入两个参数。参数列表中的参数,其实就是一个变量,变量名可以自己取。

在定义一个函数时,应该将逻辑上的某一种功能封装成一个函数,而我们之前定义repeat函数时,只是简单的将重复代码放到一个函数而已,在逻辑上没有什么意义。但是后面定义的add函数就是有意义的,它对应的功能是将传入的参数相加,它是一个加法函数。

这一次我们就再深入一层,定义函数时,从功能角度去封装。首先观察我们一开始画的那幅图,我们发现它有一处重复的地方,它的最外层是一个三角形加长方形,内部呢,又有一个正方形的窗户。我们知道,长方形和正方形统称为矩形,这样我们就可以定义一个画矩形的函数,然后调用两次这个函数画两个矩形,不就完美了吗?

给普通人的Python——第五章

import turtle

# 定义一个函数,画正方形。它有两个参数,第一个是画笔,第二个是边长
def draw_square(pen, length):
    pen.right(90)
    pen.forward(length)
    pen.right(90)
    pen.forward(length)
    pen.right(90)
    pen.forward(length)
    pen.right(90)
    pen.forward(length)


# 创建一个窗口对象,并赋值给变量window
window = turtle.Screen()

# 创建一个海龟对象,并赋值给变量tt
tt = turtle.Turtle()

# 调用我们的draw_square函数,将画笔、正方形边长传进函数中
draw_square(tt, 100)

tt.hideturtle()
# 不让窗口立刻退出
window.mainloop()

执行这段代码后成功的画了一个正方形,但是我们这个函数一点也不灵活,我们第二次调用的时候,它仍然在原地画了一个正方形,我们希望能传一个坐标,以这个坐标点为右上角,在指定的位置画正方形,另一个问题则是这个函数中仍然有重复的代码,我们再很早之前就讲过,重复的艺术——循环,要画四条边,那我们循环执行四次就可以了,再次改进代码

import turtle


# 定义一个函数,画正方形。它有4个参数,第一个是画笔,第二、三是坐标,最后一个是边长
def draw_square(pen, x, y, length):
    # 抬起画笔
    pen.penup()
    # 复位画笔,主要是为了让画笔方向朝向右侧
    pen.home()
    # 将画笔移动到指定的点
    pen.goto(x, y)
    # 放下画笔
    pen.pendown()

    # 通过for循环让块中的代码循环运行四次
    for i in range(4):
        pen.right(90)
        pen.forward(length)


# 创建一个窗口对象,并赋值给变量window
window = turtle.Screen()

# 创建一个海龟对象,并赋值给变量tt
tt = turtle.Turtle()

# 调用draw_square函数,将画笔、右上角x、y坐标,边长传入
draw_square(tt, 30, -100, 50)
draw_square(tt, 30, 100, 50)

tt.hideturtle()
# 不让窗口立刻退出
window.mainloop()

运行上面的代码,我们成功的分别以(30, -100)、(30, 100)两个点为右上角画了两个边长为50的正方形。有了我们的draw_square函数,我们再也不需要去写那么多代码画正方形了,每次调用这个函数就可以了,这真是写一个函数,永远受益啊,大家完成这个函数,是不是特别有成就感呢!这里一定要好好体会一下,函数的妙用,以及对代码的封装。

这段代码只有一个地方需要简单说明一下,我们在函数中调用了pen.home(),这是因为每次调用draw_square函数时,我们无法确定画笔在此时的朝向,如果无法确定朝向,我们也根本没办法画正方形,因为我们的画法是默认画笔朝向右侧的,然后我们调用pen.right(90)将画笔转向朝下,然后从右上角开始朝下方画。pen.home()函数就是将画笔复位的意思,调用之后,不管画笔曾经是什么状态,此刻它都已经复位,处于原点位置,且朝向右侧。

到这里,离我们今天的目标已经很近了,我们前面订的目标是写一个画矩形的函数,现在只是完成了画正方形,那该如何改造,让这个函数也能画矩形呢?

给普通人的Python——第五章

import turtle

# 定义一个函数,画矩形。它有5个参数,与之前不同的是,我们将边长拆成两个参数,表示宽(width)和高(height)
def draw_rectangle(pen, x, y, width, height):
    # 抬起画笔
    pen.penup()
    # 复位画笔,主要是为了让画笔方向朝向右侧
    pen.home()
    # 将画笔移动到指定的点
    pen.goto(x, y)
    # 放下画笔
    pen.pendown()

    if width == height:
        for i in range(4):
            pen.right(90)
            pen.forward(width)
    else:
        for i in range(2):
            pen.right(90)
            pen.forward(height)
            pen.right(90)
            pen.forward(width)


# 创建一个窗口对象,并赋值给变量window
window = turtle.Screen()

# 创建一个海龟对象,并赋值给变量tt
tt = turtle.Turtle()

# 调用我们的draw_square函数,将画笔、正方形边长传进函数中
draw_rectangle(tt, 30, -100, 50, 50)
draw_rectangle(tt, 30, 100, 50, 90)

tt.hideturtle()
# 不让窗口立刻退出
window.mainloop()

我们首先将函数名改为draw_rectangle,意思是画矩形。我们知道,正方形其实就是特殊的矩形,也就是边长相等的矩形。因此我们之前的draw_square函数其实改动不大,我们只是加了一个if-else判断,当宽和高相等时,说明是正方形,那就仍然走以前的代码,如果宽高不相等,那显然是长方形,由于长方形的特殊性,我们的代码只能循环两次,这一点结合上面的画法图很容易理解。最后我们调用draw_rectangle在指定坐标处各画了一个正方形和长方形

给普通人的Python——第五章

到这里我们彻底实现了定义一个函数画矩形的目标,大家一定要好好体会体会这里的妙用。

5.3 使用函数重构代码

最后,使用我们编写的函数,改造最开始我们画房子的代码

import turtle

# 定义一个画矩形的函数
def draw_rectangle(pen, x, y, width, height):
    # 抬起画笔
    pen.penup()
    # 复位画笔,主要是为了让画笔方向朝向右侧
    pen.home()
    # 将画笔移动到指定的点
    pen.goto(x, y)
    # 放下画笔
    pen.pendown()

    if width == height:
        for i in range(4):
            pen.right(90)
            pen.forward(width)
    else:
        for i in range(2):
            pen.right(90)
            pen.forward(height)
            pen.right(90)
            pen.forward(width)

# 创建一个窗口对象,并赋值给变量window
window = turtle.Screen()

# 创建一个海龟对象,并赋值给变量tt
tt = turtle.Turtle()

# 画一个300x450的矩形
draw_rectangle(tt, 150, 150, 300, 450)

# 抬起画笔
tt.penup()
# 将画笔移动到坐标为(150,150)的位置
tt.goto(150, 150)
tt.pendown()

# 画一个直角三角形
tt.left(135)
tt.forward(212)
tt.left(90)
tt.forward(212)

tt.penup()
# 移动到(50,0)位置,画一个半圆
tt.goto(50, 0)
tt.pendown()
tt.left(225)
tt.circle(50, 180)

# 画一个100x100的正方形
draw_rectangle(tt, 50, 0, 100, 100)

# 画窗户中的十字
tt.penup()
tt.home()
tt.pendown()
tt.right(90)
tt.forward(100)

tt.penup()
tt.goto(50, -50)
tt.pendown()
tt.right(90)
tt.forward(100)

tt.hideturtle()
# 不让窗口立刻退出
window.mainloop()

改造后,代码从逻辑上就异常清晰了。这一章的学习中,我们要学习好函数的使用,充分体会函数的复用性,这种重复使用不仅仅是一次性的,而是今后我们再也不需要去手动的一笔一笔的画矩形了,甚至我们还能把这个函数发给别人使用。而且有了函数后,代码变得更加清晰,一个函数就对应一种功能,以后我们会不断学习函数的使用,写代码中灵活定义函数,这样我们的代码就有了结构,再也不是一锅粥的都写在一起,这就好比写文章我们要分段,绝不是一篇文章只有一段那样。

请关注公众号:编程之路从0到1

给普通人的Python——第五章