函数
函数的参数(Parameters)
在 Python 中,函数的参数可以分为以下几种类型:必选参数、默认参数、可变参数 (*args)、命名关键字参数和关键字参数 (**kwargs)。并且在定义函数时, 必须按照这样的顺序来排列参数.
1 | def complex_function(pos1, pos2, default_arg='default', *args, kw_only1, kw_only2='default', **kwargs): |
必选参数 (Positional or Required Arguments)
这是最基本的参数类型。在调用函数时,必须为这些参数传递值,并且传递的顺序要与函数定义中的顺序一致(或者也可以指定名称传递, 这样就不需要按顺序来)。因为它们没有默认值,所以是“必选的”。
1 | def describe_person(name, age): |
默认参数 (Default Arguments)
在定义函数时,可以为一个或多个参数提供默认值。如果在调用函数时没有为这些参数提供值,Python 将使用预设的默认值(可选提供,若不提供则使用默认值)
定义时在参数名后使用 = 赋值, 并且要注意: 默认参数必须定义在所有必选参数之后。
1 | def send_greeting(name, message="Hello"): |
默认参数必须指向不变对象
一个常见的错误是使用可变对象(如列表或字典)作为默认参数。这会导致意想不到的行为,因为默认参数在函数定义时只被计算一次,而不是每次调用函数时都重新计算。
默认参数的值在函数定义时就被计算和存储了。如果默认参数是一个可变对象(如列表或字典),并且在函数调用中被修改,那么这个修改会影响到后续的函数调用。
1 | def append_to_list(value, my_list=[]): |
正确的做法是使用 None 作为默认值,然后在函数体内创建一个新的列表:
1 | def append_to_list(value, my_list=None): |
为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
可变参数 (*args)
当你希望函数能够处理任意数量的位置参数时,可以使用可变参数。Python 会将所有传入的多余位置参数收集到一个元组 (tuple) 中。
实现方法是, 在参数名前加一个星号 *。按照惯例,我们通常将其命名为 args, 用于接收任意多个位置参数。
1 | def calculate_sum(*numbers): |
如果已经有一个列表或元组, 想把它当作可变参数传入函数, 可以使用 * 号来解包:
1 | nums = [1, 2, 3, 4] |
关键字参数 (**kwargs)
当你希望函数能处理任意数量的关键字参数时,可以使用这种类型。Python 会将这些参数收集到一个字典 (dict) 中。
可变参数处理的是位置参数,而关键字参数处理的是命名参数, 也就是说, 可变参数只能接收没有名称的参数, 而关键字参数接收的是有名称的参数.
实现方式是在参数名前加两个星号 。按照惯例,我们通常将其命名为 kwargs**。
1 | def create_user_profile(**user_info): |
同样, 如果已经有一个字典, 想把它当作关键字参数传入函数, 可以使用 ** 号来解包:
1 | user_data = {'name': 'Frank', 'age': 35, 'city': 'Los Angeles'} |
命名关键字参数 (Named-Keyword Arguments)
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。有时候,我们希望函数能够接受一些特定的关键字参数,而不是任意的关键字参数。这时,可以使用命名关键字参数。
这种参数用于强制调用者必须使用制定关键字的形式来传递参数值,而不是通过位置。这可以提高代码的可读性,明确参数的意图。
也就是说, 必须以 key=value 的形式提供,不能通过位置传递。 - 如果前面有 args,则直接在 args 后面定义。 - 如果没有 args,则需要一个单独的星号 作为分隔符。
1 | def process_data(initial_value, *data, status, is_valid): |
1 | def set_config(*, theme, font_size, show_toolbar): |
类型注解 (Type Annotations)
Python 允许在函数定义中使用类型注解来指定参数和返回值的预期类型。这有助于提高代码的可读性,并且可以被静态类型检查工具(如 mypy)使用来检测类型错误。
使用类型注解的语法是在参数名后使用冒号
:,然后跟上类型名称(如果参数有默认值,
则默认值放在类型名称后面, 用=连接):
对于返回值,可以在参数列表后使用箭头 ->
来指定返回类型。
1 | def greet(name: str, age: int = 20) -> str: |
类型注解并不会影响函数的运行时行为,它们只是提供了额外的信息,帮助开发者理解代码的意图。
同时, 类型注解也可以用于变量声明:
1 | age: int = 25 |
高阶函数 (Higher-order Function)
要理解高阶函数,首先必须接受 Python 中的一个核心设计哲学:函数是“一等公民” (First-class Citizen)。
所谓“一等公民”,是指在 Python 中,函数可以像其他数据类型(如整数、字符串、列表等)一样被赋值给变量、作为参数传递给其他函数、作为函数的返回值返回。
一个函数如果满足以下两个条件中的至少一个,它就是高阶函数:
- 接受一个或多个函数作为参数。
- 将函数作为返回值。
简单来说:一个操作其他函数的函数,就是高阶函数。
接受函数作为参数
这是高阶函数最常见的形式。它允许我们将行为“注入”到一个函数中,使这个函数变得更加灵活和通用。
1 | # 这是一个普通的函数 |
同时, Python 提供了很多非常有用的内置高阶函数,最典型的就是 map(), filter() 和 sorted()。
map(function, iterable): 对 iterable 中的每个元素执行 function 操作, 并把结果作为新的 Iterator 返回。
这里 map() 返回的不是一个“现成的列表”,而是一个惰性计算的迭代器 (Iterator), 也就是说结果不会一次性全部算出来,而是按需计算. 当你对 r 进行迭代(比如 for x in r: 或 list(r))时,它才会逐个调用 f(x),生成结果。1
2
3
4
5def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r) # 输出: [1, 4, 9, 16, 25, 36, 49, 64, 81]filter(function, iterable): 使用 function 过滤 iterable 中的元素,只保留使 function 返回 True 的元素。
1
2
3
4
5def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]sorted(iterable, key=function): sorted 的 key 参数就是一个函数,它定义了排序的规则。
1
2sorted([36, 5, -12, 9, -21], key=abs)
# 输出: [5, 9, -12, -21, 36]
将函数作为返回值
高阶函数也可以动态地“创建”并返回一个新的函数。这通常与闭包 (Closure) 的概念紧密相关。
假设我们想创建一些功能专一的函数,比如“乘以2的函数”、“乘以3的函数”等
1 | # 这就是一个高阶函数,因为它返回一个函数 |
装饰器 (Decorators)
理解了高阶函数后,你就能理解 Python 中最强大的特性之一, 装饰器。
装饰器本质上就是一个高阶函数,它接受一个函数作为参数,并返回一个被“装饰”过的、功能更强的新函数。
1 | import time |