Courses

函数是 Python 编程语言的重要组成部分:您也许已经遇到并使用过许多内置于 Python 语言或其生态库中的优秀函数。然而,作为一名数据科学家,您将需要不断编写自己的函数,以解决数据向您提出的问题。
本教程使用 Python 3 语法。所有示例适用于 Python 3.10+,并已更新为现代规范(f-string、类型提示、仅限位置参数与仅限关键字参数)。当前稳定版本为 Python 3.14。
若要轻松亲自运行本教程中的所有示例代码,您可以免费创建一个 DataLab 工作簿,其中已预装 Python 并包含所有代码示例。若需更多 Python 函数编写练习,请查看这道 DataCamp 实操练习,或试试我们的 Python 数据科学工具箱课程!
Python 中的函数
在编程中,您使用函数将一组希望反复使用的指令打包,或者因其复杂性更适合自包含为子程序并在需要时调用。这意味着函数是一段为执行特定任务而编写的代码。为完成该任务,函数可能需要也可能不需要多个输入;当任务完成时,函数可以返回也可以不返回一个或多个值。
在 Python 中有三种类型的函数:
-
内置函数,例如用于求助的
help()、获取最小值的min()、向终端打印对象的print()……您可以在此处找到更多此类函数的概览。 -
用户自定义函数(UDF),即用户为帮助自己而创建的函数;以及
-
匿名函数,也称为 lambda 函数,因为它们不是用标准的
def关键字声明的。
函数 vs. 方法
方法指属于某个类的函数。您通过该类的实例或对象来访问它。函数没有这种限制:它指的是独立的函数。这意味着所有方法都是函数,但并非所有函数都是方法。
看看这个示例,您先定义一个函数 plus(),然后定义一个包含 sum() 方法的 Summation 类:
如果现在要调用属于 Summation 类的 sum() 方法,您首先需要定义该类的一个实例或对象。那么,让我们定义这样一个对象:
请记住,当您要调用函数 plus() 时不需要进行这种实例化!您可以在 DataCamp Light 代码块中直接执行 plus(1,2),不会有任何问题!
参数 vs. 实参
参数是在定义函数或方法时使用的名称,实参会映射到这些名称。换句话说,实参是传递给任何函数或方法调用的值,而函数或方法的代码通过其参数名称来引用这些实参。
请看下面的示例并回顾上面的 DataCamp Light 代码块:您向 Summation 类的 sum() 方法传递了两个实参,尽管您此前定义了三个参数,即 self、a 和 b。
self 发生了什么?
每个类方法的第一个实参始终是对该类当前实例的引用,在此例中即 Summation。按照惯例,这个实参称为 self。
这意味着在这种情况下您不需要显式传递对 self 的引用,因为 self 是一个隐式传入实参的参数名称,该实参指代调用方法的实例。它会被自动插入到实参列表中。
如何定义函数:用户自定义函数(UDF)
在 Python 中定义函数的四个步骤如下:
-
使用关键字
def声明函数,后接函数名。 -
为函数添加参数:它们应放在函数的圆括号内。以冒号结束该行。
-
添加函数应执行的语句。
-
如果函数需要输出结果,请以 return 语句结束函数。没有 return 语句时,函数将返回对象
None。
当然,随着进展,您的函数会变得更加复杂:您可以添加 for 循环、流程控制等,使其更为精细:
def hello():
name = input("Enter your name: ")
if name:
print(f"Hello {name}")
else:
print("Hello World")
hello()
在上述函数中,您要求用户输入姓名。如果未提供姓名,函数将打印“Hello World”。否则,用户将获得个性化的 “Hello” 响应。
请记住,您可以为 UDF 定义一个或多个函数参数。在“函数实参”一节中您将进一步了解。此外,您的函数可以返回或不返回一个或多个值。
return 语句
请注意,由于您在 UDF hello() 中打印了内容,因此并不一定需要返回它。上面的函数与下面这个函数之间不会有任何区别:
然而,如果您想继续处理函数的结果并在其上尝试一些操作,则需要使用 return 语句实际返回一个值,例如字符串、整数等。考虑以下场景,其中 hello() 返回字符串 "hello",而函数 hello_noreturn() 返回 None:
第二个函数会报错,因为您无法对 None 执行任何操作。您将得到一个 TypeError,其内容为无法对 NoneType(即 hello_noreturn() 的结果 None)与 int(2)执行乘法运算。
提示 当函数遇到 return 语句时会立即退出,即使这意味着它们不会返回任何值:
在使用 return 语句时,另一个值得一提的点是您可以用它返回多个值。为此,您可以使用元组。
请记住,这种数据结构与列表非常相似:它可以包含多个值。但元组是不可变的,这意味着您无法修改其中存储的任何数量!您可通过一对圆括号 () 构造元组。您可以使用逗号和赋值运算符将元组解包到多个变量中。
查看以下示例,了解函数如何返回多个值:
注意,return 语句 return sum, a 与 return (sum, a) 的结果相同:前者实际上在底层将 sum 和 a 打包成一个元组!
如何调用函数
在前面的部分中,您已看到很多如何调用函数的示例。调用函数意味着执行您定义的函数——可以直接在 Python 提示符处执行,也可以通过另一个函数调用(如您将在“嵌套函数”一节中看到)。
像下面的 DataCamp Light 代码块一样,只需执行 hello() 就可以调用您新定义的函数 hello():
如何为 Python 函数添加文档字符串
编写 Python 函数的另一个关键方面:文档字符串(docstring)。文档字符串描述函数的作用,例如它执行的计算或其返回值。这些说明为您的函数提供文档,使任何阅读函数文档字符串的人都能理解函数做了什么,而无需追踪函数定义中的所有代码。
函数的文档字符串放在函数头之后的紧接一行,置于三个引号之间。适合 hello() 函数的文档字符串是 ‘Prints “Hello World”’。
def hello() -> None:
"""Prints "Hello World"."""
print("Hello World")
注意,文档字符串可以比此处示例更长。如果您想更深入地学习文档字符串,最好查看一些 Python 库(如 scikit-learn 或 pandas)的 Github 代码库,那里有大量示例!
类型提示
与文档字符串密切相关——并且在现代 Python 代码中几乎同样常见——的是类型提示。自 Python 3.5 起,您可以为函数的参数和返回值添加预期的类型注解。以下是最简单的示例:
def plus(a: int, b: int) -> int:
return a + b
参数后的 : int 表示“这里应该是整数”,而括号后的 -> int 表示“此函数返回整数”。Python 本身在运行时并不会强制执行这些注解——您仍然可以向 plus() 传入字符串,Python 也不会抱怨,直到某处出错。但像 mypy、pyright 以及 VS Code、PyCharm 等编辑器内置的类型检查器会使用这些提示,在您运行代码之前就捕获错误。
对于更复杂的类型,您可以直接使用内置泛型(Python 3.9+),或从 typing 模块导入:
def greet(names: list[str]) -> None:
for name in names:
print(f"Hello {name}")
def find_user(user_id: int) -> dict | None:
# returns the user dict, or None if not found
...
Python 中的函数实参
前面您已经了解了参数与实参的区别。简而言之,实参是传递给任何函数或方法调用的值,而函数或方法的代码通过其参数名称来引用这些实参。Python 的 UDF 可以接受四种类型的实参:
- 默认实参
- 必需实参
- 关键字实参
- 可变数量的实参
默认实参
默认实参是在函数调用时未传入该实参值时采用默认值的参数。您可以使用赋值运算符 = 指定默认值,如下例所示:
必需实参
顾名思义,UDF 的必需实参是必须传入的。这些实参需要在函数调用时按准确顺序传递,就像下面这个示例:
您需要传入能映射到参数 a 和 b 的实参,才能在不报错的情况下调用函数。如果您交换了 a 和 b,结果可能不会不同,但如果您将 plus() 改为如下形式,则可能会不同:
关键字实参
如果您想确保以正确的顺序调用所有参数,可以在函数调用中使用关键字实参。您使用这些关键字根据参数名来标识实参。让我们以上面的示例来进一步说明:
请注意,通过使用关键字实参,您还可以交换参数的顺序,并在执行函数时仍获得相同结果:
可变数量的实参
当您不知道要传递给函数的实参的确切数量时,可以使用带有 *args 的以下语法:
星号(*)放在保存所有非关键字可变实参值的变量名前。请注意,您也可以将 *varint、*var_int_args 或任何其他名称传给 plus() 函数。
提示:试着将 *args 替换为包含星号的其他名称。您会发现上述代码依然可以运行!
您会看到,上述函数使用了 Python 内置的 sum() 函数来对传递给 plus() 的所有实参求和。
全局变量 vs. 局部变量
一般而言,在函数体内定义的变量具有局部作用域,函数体外定义的变量具有全局作用域。这意味着局部变量在函数块内定义,只能在该函数内部访问;而全局变量可以被脚本中的所有函数访问:
您会发现,当尝试打印在函数体内定义的局部变量 total 时,会得到一个 NameError,提示 name 'total' is not defined。而 init 变量则可以正常打印。
仅限位置参数与仅限关键字参数
自 Python 3.8 起,您可以在函数签名中使用两个特殊标记 / 与 * 来更好地控制参数必须如何传递:/ 之前的任何参数只能以位置方式传递;* 之后的任何参数只能以关键字方式传递。
def greet(name, /, greeting="Hello", *, punctuation="!"):
print(f"{greeting} {name}{punctuation}")
在调用端表现如下:
greet("Alice") # works
greet("Alice", greeting="Hi") # works
greet("Alice", "Hi", punctuation="?") # works
greet(name="Alice") # TypeError: name is positional-only
greet("Alice", "Hi", "?") # TypeError: punctuation is keyword-only
为什么要这样做?主要有两个原因。
首先,仅限位置参数让您以后可以重命名参数而不破坏任何人的代码——由于不允许将参数名作为关键字使用,您可以放心更改它。
其次,仅限关键字参数强制调用者明确传递的内容,当您有多个可选开关或设置时,这会让调用更具可读性。
简而言之,它们是强制意图的简洁方式。
Python 中的匿名函数
在 Python 中,匿名函数也称为 lambda 函数,因为它们不是用标准的 def 关键字声明,而是使用 lambda 关键字。
在上面的 DataCamp Light 代码块中,lambda x: x*2 就是匿名或 lambda 函数。x 是实参,x*2 是被求值并返回的表达式或指令。该函数的特别之处在于它没有名称,不像您在本教程前半部分看到的那些示例。如果要将上述函数写成 UDF,结果如下:
def double(x):
return x*2
我们再看一个使用两个实参的 lambda 函数示例:
当您需要一个在短时间内使用、并在运行时创建的无名函数时,就会用到匿名函数。一个相关的具体场景是与 filter()、map() 和 reduce() 配合使用:
filter() 函数会如其名,对原始输入列表 my_list 按照条件 >10 进行过滤。另一方面,使用 map(),您会将函数应用于列表 my_list 的所有元素。在此例中,您将所有元素乘以 2。
请注意,reduce() 函数位于 functools 库中。您将该函数以累积方式应用于列表 my_list 的各项,从左到右,将序列归约为单个值,本例中为 55。
在 Python 中将 main() 用作函数
如果您有 Java 等其他编程语言的经验,您会知道必须使用 main 函数来执行函数。正如您在上述示例中所见,在 Python 中并非必须如此。然而,在 Python 程序中包含一个 main() 函数有助于逻辑地组织代码——所有最重要的组件都包含在此 main() 函数中。
您可以轻松定义一个 main() 函数,并像上面的其他函数一样调用它:
但是,就目前而言,当您将其作为模块导入时,会调用 main() 函数的代码。为确保不会发生这种情况,您应在 __name__ == '__main__' 时才调用 main() 函数。
这意味着上述代码块的代码将变为:
注意,除了 __main__ 函数之外,还有一个 __init__ 函数,用于初始化类或对象的实例。简单来说,它充当构造函数或初始化器,在您创建类的新实例时自动调用。通过该函数,新创建的对象会被赋值给参数 self,您在本教程前文已见过。请看下面的示例:
class Dog:
"""A simple Dog class.
Args:
legs: Number of legs so that the dog can walk.
color: The color of the fur.
"""
def __init__(self, legs: int, color: str) -> None:
self.legs = legs
self.color = color
def bark(self) -> str:
return "bark" * 2
if __name__ == "__main__":
dog = Dog(4, "brown")
print(dog.bark())
关于 Python 函数的常见问题
函数是一个可复用的代码块,用于执行特定任务。它可以接收输入、进行处理并返回输出。
在 Python 中定义函数,使用 def 关键字,命名函数,在括号中添加可选参数,编写代码,并可选择使用 return 返回值。
函数是独立的,而方法属于类。所有方法都是函数,但并非所有函数都是方法。
Python 有内置函数(如 print())、用户自定义函数(您自行编写)以及匿名函数(短小精悍的 lambda 函数)。
参数是函数定义中的占位符,实参是您在调用函数时传入的实际值。
lambda 函数是一种用于快速任务的单行、无名函数。
__main__ 可使代码更有组织,并确保某些部分仅在脚本被直接执行时运行,而在被导入时不会运行。
全局变量在任何地方都可用,而局部变量只在其所在的函数内部有效。