Program Arcade Games
With Python And Pygame

Chapter 9: 函数

9.1 函数入门

视频: 为何使用函数?

函数的使用有两个理由。第一,它们使得代码更易于阅读和理解。 第二,它们使得代码可以重复使用。

想象一下画出如下图所示的这颗树的代码9.1. 要实现它,程序员需要执行如下命令:

pygame.draw.rect(screen, BROWN, [60, 400, 30, 45])
pygame.draw.polygon(screen, GREEN, [[150, 400], [75, 250], [0, 400]])
pygame.draw.polygon(screen, GREEN, [[140, 350], [75, 230], [10, 350]])
fig.simple_tree
Figure 9.1: 简单的树

在想要画树的时候这三行代码不会很明显地跳出来!如果我们有多课树或者更复杂的物体要画, 要理解怎么画出来就开始变得复杂。

通过定义一个函数,我们能够使得程序易于阅读。 要定义函数,首先要使用def命令。 在def命令之后是函数的名称。在这个例子里我们称它为draw_tree. 我们使用和命名变量相同的原则来定义函数的名字。

紧接着函数名的是一组圆括号和一个冒号。所有函数中的命令都会在里面缩进。如下例所示:

def draw_tree():
    pygame.draw.rect(screen, BROWN, [60, 400, 30, 45])
    pygame.draw.polygon(screen, GREEN, [[150, 400], [75, 250], [0, 400]])
    pygame.draw.polygon(screen, GREEN, [[140, 350], [75, 230], [10, 350]])

光函数本身,并不能画出一棵树。它会告诉计算机如何去使用draw_tree来画树。你必须 调用这个函数来实际运行函数里的代码从而把树画出来:

draw_tree()

使用一个包含了许多画图函数的库,最终的程序是这样的:

draw_tree()
draw_house()
draw_car()
draw_killer_bunny()

我们记得draw_tree有三行代码。 这里面的命令,比如draw_house有许多行代码。 通过使用这些函数,我们可以重复使用这些命令而不用重复里面所以的代码,使得程序更小巧。

函数名称非常重要。 如果函数名是描述性的,那么即使一个非程序员都能够能大概知道代码是做什么的。 函数的命名和变量名规则相同,应该以一个小写自己开头。

9.2 Function Parameters

fig.function

函数可以使用参数。这些参数可以通过变更不同的参数来改变函数的功能,从而增加函数的灵活性。 例如,我们叫draw_tree()的函数在一个特定的地方画数。 但是函数可以改成取一个参数来定义在哪儿画这棵树 例如draw_tree(screen, 0, 230)会在一个(0, 230)这个位置来画。

修改过的函数可能看起来是这样的:

def draw_tree(screen, x, y):
    pygame.draw.rect(screen, BROWN, [60+x, 170+y, 30, 45])
    pygame.draw.polygon(screen, GREEN, [[150+x,170+y],[75+x,20+y], [x,170+y]])
    pygame.draw.polygon(screen, GREEN, [[140+x,120+y], [75+x,y], [10+x,120+y]])

这会允许我们在我们喜欢的位置画不同的树:

draw_tree(screen, 0, 230)
draw_tree(screen, 200, 230)
draw_tree(screen, 400, 230)

这里有一个不同的函数没有使用图形,它会计算并打印出一个球的体积:

def volume_sphere(radius):
    pi = 3.141592653589
    volume = (4 / 3) * pi * radius ** 3
    print("The volume is", volume)
视频: 创建一个函数
参数是在函数被调用时赋值的,而不是定义的时候。

这个函数的名称是volume_sphere。数据在传入函数时会被存进一个新的变量叫做radius。 最后体积的结果会被打印在屏幕上。radius变量在这里并不得到值。 新的程序员对此通常很困惑,因为参数变量在函数定义的时候并没有赋值,所以看起来好像不合法。 事实上是,参数是在函数被调用的时候才会赋值。

要调用这个函数,使用:

volume_sphere(22)

radius变量现在被创造出来并赋予了一个22的值。 当执行调用函数的时候,函数的代码会运行一次。

如果我们想传递多个参数呢? 多个参数可以同时传递给同一个函数,每个参数由逗号隔开:

def volume_cylinder(radius, height):
    pi = 3.141592653589
    volume = pi * radius ** 2 * height
    print("The volume is", volume)

函数可能是这样调用的:

volume_cylinder(12, 3)

参数是按顺序执行的,所以radius会得到12,height会得到3。

9.3 返回数值

不幸地是,这些例子里的函数都有局限性。为什么 因为如果我们想用volume_cylinder函数来计算六个圆柱体体积,是无法湾趁的。 它只能打印出一个圆柱的体积。不可能把函数的结果用在一个等式里再乘以6来得到6倍的体积。

我们可以使用return语句来解决这个问题。例如:

def volume_cylinder(radius, height):
    pi = 3.141592653589
    volume = pi * radius ** 2 * height
    return volume

Return不是一个函数,也不使用圆括号。不要写成return(volume).

因为使用了使用bd>return, 这个函数之后可以使用在计算6倍体积的等式里,就像这样:

six_pack_volume = volume_cylinder(2.5, 5) * 6

volume_cylinder返回的值会代入等式并乘以6。

prints一个值的函数和returns一个值的函数有巨大的区别。 观察下下段代码并运行试试卡。

# 打印结果的函数
def sum_print(a, b):
    result = a + b
    print(result)

# 返回解雇的函数
def sum_return(a, b):
    result = a + b
    return result

# 这可以打印4+4的和
sum_print(4, 4)

# 而这不可以
sum_return(4, 4)

# 这不会不会把和赋值给x1
# x1实际上得到的值是'None'
x1 = sum_print(4, 4)

# 这个可以
x2 = sum_return(4, 4)

当刚开始用函数的时候经常会像下面的代码一样困住:

def calculate_average(a, b):
    """ 计算两个数的平均值 """
    result = (a * b) / 2
    return result

# 假设你这有些代码
x = 45
y = 56

# 等下,我如何打印结果?
calculate_average(x, y)

我们如何打印calculate_average的结果? 这程序平不能打印result因为变量只存在于函数之内。 因此,我们需要使用一个变量来抓取结果:

def calculate_average(a, b):
    """ 计算两个数的平均值 """
    result = (a * b) / 2
    return result

# 假设你这里有些代码
x = 45
y = 56

average = calculate_average(x, y)
print(average)

9.4 函数描述

Python中的函数通常以一句注释来作为第一条语句。 这条注释用3个引号包围起来,被叫做docstring。就像这个样子:

def volume_cylinder(radius, height):
    """Returns volume of a cylinder given radius, height."""
    pi = 3.141592653589
    volume = pi * radius ** 2 * height
    return volume

在函数里使用docstrings最棒的地方是这段注释可以被抓取出来用在网站上描述你的代码,通过Sphinx这样的工具。 大多数语言有类似的工具能让你轻松地文档化你的代码。在你写更大的程序是会节省不少时间。

9.5 变量范围

视频: 变量范围

函数的使用会引入范围。范围是指一个在代码中某个变量“存活”着以及可以被访问的范围。 例如,请看下面这段代码:

# 定义一个简单的函数
# 把x设置成22
def f():
    x = 22

# 调用这个函数
f()
# 这会失败,因为x只存在于f()之中
print(x)

最后一行会生成一个错误,因为x只存在于f()函数之内。这个变量是在f()被调用是创建的, 它所使用的内存也会在f()完成时被释放。

这里是问题开始变复杂了。

有一条更令人困惑的是规则是关于访问f()函数之外创造的变量。在虾米的代码中,x是在f()函数之前创造的,所以可以在f()函数里读取。

# 创造一个变量x,并设为44
x = 44

# 定义一个打印x的函数
def f():
    print(x)

# 调用这个函数
f()

在函数创建的变量可以在函数内读取的条件是函数必须不能改变它的值。这段代码, 和先前的很相似,但是会失败。计算机会抱怨说它不知道x是什么。

# 创造一个变量x,并设为44
x = 44

# 定义一个打印x的函数
def f():
    x += 1
    print(x)

# 调用这个函数
f()

其他语言比起Python来,对变量的创建和范围要求有更加复杂的规定。 因为Python很直接,所以它似乎一门非常好的入门语言。

9.6 通过复制来传递

函数通过创建一个原值的复制品来传递它们的值。例如:

# 定义一个打印x的函数
def f(x):
    x += 1
    print(x)

# 设置y
y = 10
# 调用这个函数
f(y)
# 打印y看看值是否改变了
print(y)

y的值并没有改变,即使f()函数增加了传递给它的值。每一个在函数中列为参数的变量是一个崭新的变量。 那个变量的值是从被调用的地方复制过来的。

这一点在之前这个例子里很直接地体现了出来。它令人疑惑的地方是当调用函数的代码和函数本身有相同名字的变量。 虾米的代码就是跟之前是完全一致的,只不过在使用y的地方使用了x

# 定义一个打印x的函数
def f(x):
    x += 1
    print(x)

# 设置x
x = 10
# 调用这个函数
f(x)
# 打印x看看值是否改变了
print(x)

结果和使用y的程序是一样的。即使函数和它周围的代码都使用了x作为一个便俩的名字,它们实际上是两个不同的变量。 一个是存在于函数内的变量x,另一个变量x则是存在于函数之外。

9.7 函数调用函数

完全有可能发生的是一个函数调用另一个函数。例如,下面的函数被定义了出来:

def arm_out(whichArm, palmUpOrDown):
	# 代码写在这里

def hand_grab(hand, arm):
	# 代码写在这里

那么另一个函数可以被创建出来调用这些函数:

def macarena():
	arm_out("right", "down")
	arm_out("left", "down")
	arm_out("right", "up")
	arm_out("left", "up")
	hand_grab("right", "left arm")
	hand_grab("left", "right arm")
	# 等等

9.8 主函数和全局

全局变量是纯正的恶魔。

当程序越变越大,把代码条例化并放入函数是很重牙的。Python允许我们在“缩进等级0”的位置写代码。 这表述了我们目前为止写的绝大部分代码。我们写的代码都是左对齐,并没有被包含于函数之内。

这种哲学就好比把你的衣服都堆在地板中间,或者是工具都在工作台上垒起来了。 这只有在你东西不多时还管用。 其实即使东西不多还是乱糟糟的。

你所有的代码和你所有的变量都应该安放在函数内。 这样会使得你的代码井然有序。它也会帮助你更容易追踪到程序的漏洞。在“缩进等级0”的地方创建的变量叫做全局变量。全局变量是一个非常坏的东西。为什么? 因为任意位置的任意一段代码都能改变它们的值。如果你有50000行代码,每一行代码都可以改变那个全局变量的值。 但如果你只是把变量放在函数里,只有那函数中的代码可以改变它的值。 因此,如果你发现一个变量有一个未预料到的值,你可能只需要在你函数里的50行代码。 否则你必须检查程序里的每一行代码!

一个更好的方式是把程序遵照如下的样式写:

def main():
	print("Hello world.")

main()

在这种情况下,我所有在缩进0的位置写的代码都放置在了main函数里。最后一行代码是调用main

但是等一下!还有一个问题我们嘘牙解决。在第14章的时候,我们会讨论到如何把我们的程序分割到不同的文件中去。我们能够使用 import命令来导入其他模块中我们创造的函数。 如果我们使用 import命令在这个程序上,它会自动开锁运行main函数。我们并不想遮掩。我们希望导入它的程序可以控制何时来调用导入的函数。

要解决这个问题,我们可以让我们的程序检查一个由Python自动定义的全局变量。 (我知道,我刚才提到全局变量非常坏,对不对?) 这个变量叫做__name__, 在name前后使用前后下划线。我们能够通过检查它,来查看这个代码是否被导入或者运行了。 如果代码正被韵新,Python会自动把那个变量的值自动设置到__main__. 通过使用一个if语句,我们只会在代码被韵西的时候被允main函数。 否则代码只是 定义在了main函数里。 导入它的代码可在需要的时候调用它。

这是你所有的Python代码应该运行的样子:

def main():
	print("Hello world.")

if __name__ == "__main__":
    main()

我喜欢把Python作为第一个学习的语言的理由就是你不要求使用如此的复杂程度直到你真正需要的时候。其他语言,比如Java,不管你的程序多小都有这样的要求。

为了让本书更简洁,我们本书m>不用这样的样式演示我们的例子。 但是当以后你的程序足够复杂的话,为了让生活更简单,还是别 “把所有的衣服堆成山成'了。

如果你对编程超级热情,你可以试着从现在起就这么写你的代码。 尽管可能一开始时感觉有些挑战,但是会让以后写程序更轻松。这也同时是一种学习如果正确地管理你的代码和范围的好方法。

这个例子展示了如果用这种样式来写PyGame的基础模板:
programarcadegames.com/python_examples/f.php?file=pygame_base_template_proper.py

如果让我聊教课我是不会要求使用这种样板的。在你第一学期的课时我是运行你把衣服都堆在一起的。你穿着衣服我就很高兴了。 (对于有洁癖的同学,我们会在讲述 “类”的章节时把这个堆衣服的问题好好解决。)

9.9 举例

下面每一个例子,都是关于打印出什么结果。检查下你的思考是否正确。 如果你没有猜对,花一些事件去理解为什么。

# 例1
def a():
    print("A")

def b():
    print("B")

def c():
    print("C")

a()
# 例2
def a():
    b()
    print("A")

def b():
    c()
    print("B")

def c():
    print("C")

a()
# 例3
def a():
    print("A")
    b()

def b():
    print("B")
    c()

def c():
    print("C")

a()
# 例4
def a():
    print("A start")
    b()
    print("A end")

def b():
    print("B start")
    c()
    print("C end")

def c():
    print("C start and end")

a()
# 例5
def a(x):
    print("A start, x =",x)
    b(x + 1)
    print("A end, x =",x)

def b(x):
    print("B start, x =",x)
    c(x + 1)
    print("C end, x =",x)

def c(x):
    print("C start and end, x =",x)

a(5)
# 例6
def a(x):
    x = x + 1

x = 3
a(x)

print(x)
# 例7
def a(x):
    x = x + 1
    return x

x = 3
a(x)

print(x)
# 例8
def a(x):
    x = x + 1
    return x

x = 3
x = a(x)

print(x)
# 例9
def a(x, y):
    x = x + 1
    y = y + 1
    print(x, y)

x = 10
y = 20
a(y, x)

print(z)
# 例10
def a(x, y):
    x = x + 1
    y = y + 1
    return x
    return y

x = 10
y = 20
z = a(x, y)

print(z)
# 例11
def a(x, y):
    x = x + 1
    y = y + 1
    return x, y

x = 10
y = 20
z = a(x, y)

print(z)
# 例12
def a(x, y):
    x = x + 1
    y = y + 1
    return x, y

x = 10
y = 20
x2, y2 = a(x, y) # Most computer languages don't support this

print(x2)
print(y2)
# 例13
def a(my_data):
    print("function a, my_data =  ", my_data)
    my_data = 20
    print("function a, my_data =  ", my_data)

my_data = 10

print("global scope, my_data =", my_data)
a(my_data)
print("global scope, my_data =", my_data)
# 例14
def a(my_list):
    print("function a, list =  ", my_list)
    my_list = [10, 20, 30]
    print("function a, list =  ", my_list)

my_list = [5, 2, 4]

print("global scope, list =", my_list)
a(my_list)
print("global scope, list =", my_list)
# 例15
# 新概念!
# 更多介绍在第12章节
def a(my_list):
    print("function a, list =  ", my_list)
    my_list[0] = 1000
    print("function a, list =  ", my_list)

my_list = [5, 2, 4]

print("global scope, list =", my_list)
a(my_list)
print("global scope, list =", my_list)
"""
This is a sample text-only game that demonstrates the use of functions.
The game is called "Mudball" and the players take turns lobbing mudballs
at each other until someone gets hit.
"""

import math
import random


def print_instructions():
    """ This function prints the instructions. """

    # You can use the triple-quote string in a print statement to
    # print multiple lines.
    print("""
Welcome to Mudball! The idea is to hit the other player with a mudball.
Enter your angle (in degrees) and the amount of PSI to charge your gun
with.
        """)


def calculate_distance(psi, angle_in_degrees):
    """ Calculate the distance the mudball flies. """
    angle_in_radians = math.radians(angle_in_degrees)
    distance = .5 * psi ** 2 * math.sin(angle_in_radians) * math.cos(angle_in_radians)
    return distance


def get_user_input(name):
    """ Get the user input for psi and angle. Return as a list of two
    numbers. """
    # Later on in the 'exceptions' chapter, we will learn how to modify
    # this code to not crash the game if the user types in something that
    # isn't a valid number.
    psi = float(input(name + " charge the gun with how many psi? "))
    angle = float(input(name + " move the gun at what angle? "))
    return psi, angle


def get_player_names():
    """ Get a list of names from the players. """
    print("Enter player names. Enter as many players as you like.")
    done = False
    players = []
    while not done:
        player = input("Enter player (hit enter to quit): ")
        if len(player) > 0:
            players.append(player)
        else:
            done = True

    print()
    return players


def process_player_turn(player_name, distance_apart):
    """ The code runs the turn for each player.
    If it returns False, keep going with the game.
    If it returns True, someone has won, so stop. """
    psi, angle = get_user_input(player_name)

    distance_mudball = calculate_distance(psi, angle)
    difference = distance_mudball - distance_apart

    # By looking ahead to the chapter on print formatting, these
    # lines could be made to print the numbers is a nice formatted
    # manner.
    if difference > 1:
        print("You went", difference, "yards too far!")
    elif difference < -1:
        print("You were", difference * -1, "yards too short!")
    else:
        print("Hit!", player_name, "wins!")
        return True

    print()
    return False


def main():
    """ Main program. """

    # Get the game started.
    print_instructions()
    player_names = get_player_names()
    distance_apart = random.randrange(50, 150)

    # Keep looking until someone wins
    done = False
    while not done:
        # Loop for each player
        for player_name in player_names:
            # Process their turn
            done = process_player_turn(player_name, distance_apart)
            # If someone won, 'break' out of this loop and end the game.
            if done:
                break

if __name__ == "__main__":
    main()

9.10 复习

9.10.1 选择题小测验

Click here for a multiple-choice quiz.

9.10.2 练习表

Click here for the chapter worksheet.

9.10.3 实验

Click here for the chapter lab.


You are not logged in. Log in here and track your progress.