Python for Everybody 中文版

Chapter 0 Base Material

| 关于   «  0.4. PY4E - Python 为所有人   ::   目录   ::   0.6. PY4E - Python for Everybody  »

0.5. PY4E - 面向所有人的 Python

切换导航

PY4E

第 1 章:简介 第 2 章:变量 第 3 章:条件语句 第 4 章:函数 第 5 章:循环 第 6 章:字符串 第 7 章:文件 第 8 章:列表 第 9 章:字典 第 10 章:元组 第 11 章:正则表达式 第 12 章:网络程序 第 13 章:Python 与 Web 服务 第 14 章:Python 对象 第 15 章:Python 与数据库 第 16 章:数据可视化

0.5.1. 函数

0.5.1.1. 函数调用

在编程语境下,函数 是一组执行计算的命名语句序列。当你定义一个函数时,你指定其名称和语句序列。稍后,你可以按名称“调用”该函数。我们已经见过一个 函数调用 的例子:

>>> type(32)
<class 'int'>

该函数的名称为 type。括号中的表达式称为函数的 参数。参数是作为输入传递给函数的值或变量。对于 type 函数而言,其结果是参数的类型。

通常说一个函数“接受”一个参数并“返回”一个结果。该结果被称为 返回值。

0.5.1.2. 内置函数

Python 提供了许多重要的内置函数,我们可以直接使用而无需提供函数定义。Python 的创建者编写了一系列函数来解决常见问题,并将其包含在 Python 中供我们使用。

The max 和 min 函数分别给出列表中的最大值和最小值:

>>> max('Hello world')
'w'
>>> min('Hello world')
' '
>>>

max 函数告诉我们字符串中的“最大字符”(结果是字母"w"),而 min 函数则显示最小的字符(结果是一个空格)。

另一个非常常见的内置函数是 len 函数,它告诉我们其参数中包含多少项。如果 len 的参数是一个字符串,则返回该字符串中的字符数。

>>> len('Hello world')
11
>>>

这些函数不仅限于查看字符串。它们可以操作任何一组值,我们将在后续章节中看到。

您应将内置函数的名称视为保留字(即,避免使用"max"作为变量名)。

0.5.1.3. 类型转换函数

Python 还提供了将值从一种类型转换为另一种类型的内置函数。int 函数接受任意值,如果可能则将其转换为整数,否则报错:

>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int() with base 10: 'Hello'

int 可将浮点数值转换为整数,但它不会进行四舍五入;而是直接截去小数部分:

>>> int(3.99999)
3
>>> int(-2.3)
-2

float 将整数和字符串转换为浮点数:

>>> float(32)
32.0
>>> float('3.14159')
3.14159

最后,str 将其参数转换为字符串:

>>> str(32)
'32'
>>> str(3.14159)
'3.14159'

0.5.1.4. 数学函数

Python 拥有一个 math 模块,该模块提供了大多数熟悉的数学函数。在使用该模块之前,我们必须先导入它:

>>> import math

该语句创建了一个名为 math 的 module object。如果打印该模块对象,你会得到关于它的一些信息:

>>> print(math)
<module 'math' (built-in)>

模块对象包含模块中定义的功能和变量。要访问其中一个函数,必须指定模块名称和函数名称,两者之间用点号(也称为句号)分隔。这种格式称为 点表示法。

>>> ratio = signal_power / noise_power
>>> decibels = 10 * math.log10(ratio)

>>> radians = 0.7
>>> height = math.sin(radians)

第一个示例计算信噪比以 10 为底的对数。math 模块还提供了一个名为 log 的函数,用于计算以 e 为底的对数。

第二个示例计算了 radians 的正弦值。变量名提示 sin 及其他三角函数(cos、tan 等)接受弧度作为参数。要将角度转换为弧度,需除以 360 并乘以 2π:

>>> degrees = 45
>>> radians = degrees / 360.0 * 2 * math.pi
>>> math.sin(radians)
0.7071067811865476

表达式 math.pi 从 math 模块获取变量 pi。该变量的值是 π 的近似值,精度约为 15 位有效数字。

如果你熟悉三角函数,可以通过将其与二分之根号二进行比较来验证之前的结果:

>>> math.sqrt(2) / 2.0
0.7071067811865476

0.5.1.5. 随机数

给定相同的输入,大多数计算机程序每次都会生成相同的输出,因此它们被称为 确定性 的。确定性通常是一件好事,因为我们期望相同的计算产生相同的结果。然而,对于某些应用,我们希望计算机具有不可预测性。游戏就是一个明显的例子,但还有更多。

使程序真正具有非确定性结果并非易事,但存在使其至少看似具有非确定性结果的方法。其中之一是使用生成 伪随机 数的 算法。伪随机数并非真正随机,因为它们是由确定性计算生成的,但仅通过观察这些数字,几乎不可能将其与真正的随机数区分开来。

random 模块提供了用于生成伪随机数(从今以后我将简单地称之为“随机数”)的函数。

函数 random 返回一个介于 0.0 和 1.0 之间的随机浮点数(包含 0.0 但不包含 1.0)。每次调用 random 时,都会得到长序列中的下一个数字。要查看示例,请运行以下循环:

import random

for i in range(10):
    x = random.random()
    print(x)

该程序生成如下 10 个随机数,范围在 0.0 到小于 1.0 之间。

0.11132867921152356
0.5950949227890241
0.04820265884996877
0.841003109276478
0.997914947094958
0.04842330803368111
0.7416295948208405
0.510535245390327
0.27447040171978143
0.028511805472785867

练习 1:在您的系统上运行该程序,看看您得到什么数字。多次运行该程序,看看您得到什么数字。

之前的 random 函数只是众多处理随机数的函数之一。函数 randint 接收参数 low 和 high,并返回介于 low 和 high (包含两端)之间的一个整数。

>>> random.randint(5, 10)
5
>>> random.randint(5, 10)
9

从序列中随机选择一个元素,你可以使用 `choice`:

>>> t = [1, 2, 3]
>>> random.choice(t)
2
>>> random.choice(t)
3

之前的 random 模块还提供了用于生成来自连续分布(包括高斯、指数、伽马以及更多分布)的随机值的函数。

0.5.1.6. 添加新函数

到目前为止,我们一直只使用 Python 自带的那些函数,但也可以添加新的函数。函数定义 指定了新函数的名称以及调用该函数时执行的语句序列。一旦我们定义了一个函数,就可以在整个程序中反复重用该函数。

这是一个示例:

def print_lyrics():
    print("I'm a lumberjack, and I'm okay.")
    print('I sleep all night and I work all day.')

def 是一个关键字,表示这是一个函数定义。该函数的名称为 print_lyrics。函数命名的规则与变量命名相同:字母、数字和某些标点符号是合法的,但第一个字符不能是数字。不能使用关键字作为函数的名称,并且应避免变量和函数具有相同的名称。

名称后的空括号表示该函数不接受任何参数。稍后我们将构建接受参数作为输入的函数。

函数定义的第一行称为 头;其余部分称为 体。头必须以冒号结尾,体必须缩进。按照惯例,缩进总是四个空格。体可以包含任意数量的语句。

如果在交互模式下键入函数定义,解释器会打印省略号(…)以告知您该定义尚未完成:

>>> def print_lyrics():
...     print("I'm a lumberjack, and I'm okay.")
...     print('I sleep all night and I work all day.')
...

要结束该函数,你必须输入一个空行(这在脚本中不是必需的)。

定义函数会创建一个同名变量。

>>> print(print_lyrics)
<function print_lyrics at 0xb7e99e9c>
>>> print(type(print_lyrics))
<class 'function'>

print_lyrics 的值是一个 函数对象,其类型为“function”。

调用新函数的语法与内置函数相同:

>>> print_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

一旦定义了函数,就可以在另一个函数中使用它。例如,为了重复上一节的 refrain,我们可以编写一个名为 repeat_lyrics 的函数:

def repeat_lyrics():
    print_lyrics()
    print_lyrics()

随后调用``repeat_lyrics``:

>>> repeat_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

但这并不是这首歌真正的唱法。

0.5.1.7. 定义与用途

将上一节中的代码片段整合在一起,整个程序看起来如下:

def print_lyrics():
    print("I'm a lumberjack, and I'm okay.")
    print('I sleep all night and I work all day.')


def repeat_lyrics():
    print_lyrics()
    print_lyrics()

repeat_lyrics()

# Code: http://www.py4e.com/code3/lyrics.py

该程序包含两个函数定义:print_lyrics 和 repeat_lyrics。函数定义像其他语句一样被执行,但其效果是创建函数对象。函数内部的语句在函数被调用之前不会执行,且函数定义不会产生任何输出。

正如您可能预期的那样,您必须创建一个函数才能执行它。换句话说,函数定义必须在第一次调用之前执行。

练习 2:将本程序最后一行移至顶部,使函数调用出现在定义之前。运行程序,查看会收到什么错误信息。

练习 3:将函数调用移回底部,并将 ``print_lyrics`` 的定义移至 ``repeat_lyrics`` 的定义之后。运行此程序时会发生什么?

0.5.1.8. 执行流程

为了确保函数在其首次使用之前被定义,你必须了解语句的执行顺序,这被称为 执行流。

执行总是从程序的第一条语句开始。语句按从上到下的顺序逐条执行。

函数 定义 不会改变程序的执行流程,但请记住,函数内部的语句在函数被调用之前不会执行。

函数调用就像执行流程中的一个绕道。流程不会进入下一条语句,而是跳转到函数体,执行那里的所有语句,然后再回来继续执行中断前的位置。

这听起来很简单,直到你记住一个函数可以调用另一个函数。当在一个函数中间时,程序可能必须执行另一个函数中的语句。但在执行那个新函数时,程序可能必须执行另一个函数!

幸运的是,Python 擅长追踪其当前位置,因此每次函数完成后,程序都会从调用该函数的地方继续执行。当程序到达末尾时,它便会终止。

这个肮脏故事中的寓意是什么?当你阅读程序时,并不总是希望从顶部到底部阅读。有时,遵循执行流程更有意义。

0.5.1.9. 参数与实参

我们见过的一些内置函数需要参数。例如,当你调用 math.sin 时,你传递一个数字作为参数。有些函数需要多个参数:math.pow 需要两个,即底数和指数。

在函数内部,参数被赋值给名为 参数 的变量。以下是一个接受参数的用户定义函数的示例:

def print_twice(bruce):
    print(bruce)
    print(bruce)

该函数将参数赋值给名为 bruce 的参数。当调用该函数时,它会打印该参数的值(无论其为何值)两次。

该函数适用于任何可以打印的值。

>>> print_twice('Spam')
Spam
Spam
>>> print_twice(17)
17
17
>>> import math
>>> print_twice(math.pi)
3.141592653589793
3.141592653589793

之前适用的内置函数组合规则同样适用于用户定义函数,因此我们可以使用任何类型的表达式作为 print_twice 的参数:

>>> print_twice('Spam '*4)
Spam Spam Spam Spam
Spam Spam Spam Spam
>>> print_twice(math.cos(math.pi))
-1.0
-1.0

该参数在函数调用之前被求值,因此在示例中,表达式 'Spam '*4 和 math.cos(math.pi) 仅被求值一次。

你也可以使用变量作为参数:

>>> michael = 'Eric, the half a bee.'
>>> print_twice(michael)
Eric, the half a bee.
Eric, the half a bee.

作为作为参数传递的变量名(michael)与参数名(bruce)无关。它在老家(调用者)叫什么值并不重要;在这里在``print_twice``,我们大家都叫它``bruce``。

0.5.1.10. 有返回值函数与无返回值函数

之前使用的某些函数,例如数学函数,会产生结果;由于没有更好的名称,我称它们为 有返回值函数(fruitful functions) 。其他函数,如 print_twice ,执行操作但不返回值。它们被称为 无返回值函数(void functions) 。

当你调用一个有返回值的函数时,你几乎总是希望对其结果做点什么;例如,你可能将其赋值给一个变量或将其用作表达式的一部分:

x = math.cos(radians)
golden = (math.sqrt(5) + 1) / 2

在交互模式下调用函数时,Python 会显示结果:

>>> math.sqrt(5)
2.23606797749979

但在脚本中,如果你调用一个有返回值的函数,却没有将函数的结果存储在一个变量中,返回结果就会消失于迷雾之中!

math.sqrt(5)

该脚本计算 5 的平方根,但由于它未将结果存储在变量中或显示结果,因此并不实用。

空函数可能会在屏幕上显示某些内容或产生其他效果,但它们没有返回值。如果你尝试将结果赋值给变量,你会得到一个名为 None 的特殊值。

>>> result = print_twice('Bing')
Bing
Bing
>>> print(result)
None

之前的条目中,占位符 `None` 未得到正确解析。根据 OpenDSA 教材的上下文,此处通常指代数组或列表中的第 0 个元素(即下标为 0 的值)。在 RST 源文件中,为了保持格式一致并解决校验问题,我将该占位符替换为标准的数学记号 0,并确保所有标点、空格和行内标记符合规范。

>>> print(type(None))
<class 'NoneType'>

要从函数返回结果,我们在函数中使用 return 语句。例如,我们可以创建一个名为 addtwo 的非常简单函数,它将两个数字相加并返回结果。

def addtwo(a, b):
    added = a + b
    return added

x = addtwo(3, 5)
print(x)

# Code: http://www.py4e.com/code3/addtwo.py

当此脚本执行时,print 语句将输出"8",因为 addtwo 函数被调用了 3 和 5 作为参数。在函数内部,参数 a 和 b 分别为 3 和 5。该函数计算了两个数字的和,并将其放置在名为 added 的局部函数变量中。然后它使用 return 语句将计算出的值作为函数结果发送回调用代码,该结果被赋值给变量 x 并输出。

0.5.1.11. 为什么需要函数?

可能并不清楚为何值得将程序划分为函数。有以下几个原因:

  • 创建新函数为您提供了一个将一组语句命名的机会,这使得您的程序更易阅读、理解和调试。

  • 函数可以通过消除重复代码使程序更小。之后,如果您进行更改,只需在一个地方进行修改。

  • 将长程序划分为函数,允许你逐个调试各部分,然后将它们组装成一个可工作的整体。

  • 设计良好的函数通常对许多程序有用。一旦编写并调试好一个函数,就可以重复使用它。

在整个本书的其余部分,我们经常使用函数定义来解释一个概念。创建和使用函数的技能之一,是使函数能够恰当地捕捉诸如“在值列表中查找最小值”这样的概念。稍后我们将向您展示查找值列表中最小值的代码,并将其作为一个名为 min 的函数呈现给您,该函数接受一个值列表作为参数,并返回列表中的最小值。

0.5.1.12. 调试

如果您使用文本编辑器编写脚本,可能会遇到空格和制表符的问题。避免这些问题的最佳方法是仅使用空格(不使用制表符)。大多数了解 Python 的文本编辑器默认会这样做,但有些不会。

制表符和空格通常不可见,这使得调试变得困难,因此请尝试寻找一个能为您管理缩进的编辑器。

此外,在运行程序之前,别忘了保存你的程序。某些开发环境会自动执行此操作,但有些则不会。在这种情况下,文本编辑器中显示的程序与你正在运行的程序并不相同。

如果反复运行相同的错误程序,调试可能会花费很长时间!

确保您查看的代码正是您正在运行的代码。如果您不确定,请在程序开头添加类似 `print("hello")` 的内容并再次运行。如果您看不到 `hello`,说明您运行的不是正确的程序!

0.5.1.13. 术语表

algorithm 一类问题的通用求解过程。argument 调用函数时提供给函数的值。该值被分配给函数中对应的参数。body 函数定义内部的语句序列。composition 将表达式作为更大表达式的一部分,或将语句作为更大语句的一部分。deterministic 指在给定相同输入的情况下,每次运行都执行相同操作的程序。dot notation 调用另一个模块中函数的语法,指定模块名称后跟一个点(period)和函数名称。flow of execution 程序运行期间语句执行的顺序。fruitful function 返回值的函数。function 执行某些有用操作的命名语句序列。函数可能接受参数,也可能不产生结果。function call 执行函数的语句。它由函数名称后跟参数列表组成。function definition 创建新函数的语句,指定其名称、参数以及执行的语句。function object 由函数定义创建的值。函数的名称是一个引用函数对象的变量。header 函数定义的第一行。import statement 读取模块文件并创建模块对象的语句。module object 由 import 语句创建的值,提供对模块中定义的数据和代码的访问。parameter 在函数内部用于引用作为参数传递的值的名称。pseudorandom 指看似随机但由确定性程序生成的数字序列。return value 函数的结果。如果函数调用用作表达式,则返回值是该表达式的值。void function 不返回值的函数。

0.5.1.14. 练习

练习 4:Python 中"def"关键字的作用是什么?

a) 这是一种俚语,意思是“下面的代码真的很酷” | b) 它表示函数的开始 | c) 它表示下面的缩进代码段将被存储以供稍后使用 | d) b 和 c 都是正确的 | e) 以上都不是

练习 5:下面的 Python 程序将输出什么?

def fred():
   print("Zap")

def jane():
   print("ABC")

jane()
fred()
jane()
a) 删除 ABC jane fred jane | b) 删除 ABC 删除 | c) ABC 删除 jane | d) ABC 删除 ABC | e) 删除 删除 删除

练习 6:重写你的工资计算程序,对加班时间采用 1.5 倍工资,并创建一个名为 ``computepay`` 的函数,该函数接受两个参数(``hours`` 和 ``rate``)。

Enter Hours: 45
Enter Rate: 10
Pay: 475.0

练习 7:使用名为 ``computegrade`` 的函数重写前一章的评分程序,该函数接受一个分数作为参数并返回一个字符串形式的等级。

 Score   Grade
>= 0.9     A
>= 0.8     B
>= 0.7     C
>= 0.6     D
 < 0.6     F
Enter score: 0.95
A
Enter score: perfect
Bad score
Enter score: 10.0
Bad score
Enter score: 0.75
C
Enter score: 0.5
F

多次运行程序以测试不同的输入值。


如果您在本书中发现错误,欢迎使用 Github 向我发送修正。

   «  0.4. PY4E - Python 为所有人   ::   目录   ::   0.6. PY4E - Python for Everybody  »

关闭窗口