0.5. PY4E - 面向所有人的 Python¶
切换导航
第 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"关键字的作用是什么?
练习 5:下面的 Python 程序将输出什么?
def fred():
print("Zap")
def jane():
print("ABC")
jane()
fred()
jane()
练习 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 向我发送修正。
