函数

ZaynPei Lv6

函数的参数(Parameters)

在 Python 中,函数的参数可以分为以下几种类型:必选参数、默认参数、可变参数 (*args)、命名关键字参数和关键字参数 (**kwargs)。并且在定义函数时, 必须按照这样的顺序来排列参数.

1
2
3
def complex_function(pos1, pos2, default_arg='default', *args, kw_only1, kw_only2='default', **kwargs):
# 函数体
pass

必选参数 (Positional or Required Arguments)

这是最基本的参数类型。在调用函数时,必须为这些参数传递值,并且传递的顺序要与函数定义中的顺序一致(或者也可以指定名称传递, 这样就不需要按顺序来)。因为它们没有默认值,所以是“必选的”。

1
2
3
4
5
6
7
8
9
10
def describe_person(name, age):
"""显示一个人的姓名和年龄。"""
print(f"Name: {name}, Age: {age}") # f-string 格式化字符串

# 正确调用
describe_person("Alice", 30)
describe_person(age=30, name="Alice") # 使用关键字参数, 不需要按顺序

# 错误调用 - 缺少参数
# describe_person("Alice") # TypeError: describe_person() missing 1 required positional argument: 'age'

默认参数 (Default Arguments)

在定义函数时,可以为一个或多个参数提供默认值。如果在调用函数时没有为这些参数提供值,Python 将使用预设的默认值(可选提供,若不提供则使用默认值)

定义时在参数名后使用 = 赋值, 并且要注意: 默认参数必须定义在所有必选参数之后

1
2
3
4
5
6
7
8
9
def send_greeting(name, message="Hello"):
"""发送问候语。"""
print(f"{message}, {name}!")

# 使用默认参数
send_greeting("Bob") # 输出: Hello, Bob!

# 覆盖默认参数
send_greeting("Charlie", "Good morning") # 输出: Good morning, Charlie!

默认参数必须指向不变对象

一个常见的错误使用可变对象(如列表或字典)作为默认参数。这会导致意想不到的行为,因为默认参数在函数定义时只被计算一次,而不是每次调用函数时都重新计算。

默认参数的值在函数定义时就被计算和存储了。如果默认参数是一个可变对象(如列表或字典),并且在函数调用中被修改,那么这个修改会影响到后续的函数调用。

1
2
3
4
5
6
def append_to_list(value, my_list=[]):
"""将值添加到列表中。"""
my_list.append(value)
return my_list
print(append_to_list(1)) # 输出: [1]
print(append_to_list(2)) # 输出: [1, 2] -- 这里的 my_list 不是一个新的列表,而是上次调用时的列表

正确的做法是使用 None 作为默认值,然后在函数体内创建一个新的列表:

1
2
3
4
5
6
7
8
def append_to_list(value, my_list=None):
"""将值添加到列表中。"""
if my_list is None:
my_list = []
my_list.append(value)
return my_list
print(append_to_list(1)) # 输出: [1]
print(append_to_list(2)) # 输出: [2] -- 每次调用 都是一个新的列表

为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

可变参数 (*args)

当你希望函数能够处理任意数量位置参数时,可以使用可变参数。Python 会将所有传入的多余位置参数收集到一个元组 (tuple) 中。

实现方法是, 在参数名前加一个星号 *。按照惯例,我们通常将其命名为 args, 用于接收任意多个位置参数。

1
2
3
4
5
6
7
8
9
10
def calculate_sum(*numbers):
"""计算所有输入数字的和。"""
total = 0
print(f"Received numbers: {numbers}") # numbers 是一个元组
for number in numbers:
total += number
return total

print(calculate_sum(1, 2, 3)) # 输出: Received numbers: (1, 2, 3) -> 6
print(calculate_sum(10, 20, 30, 40)) # 输出: Received numbers: (10, 20, 30, 40) -> 100

如果已经有一个列表或元组, 想把它当作可变参数传入函数, 可以使用 * 号来解包:

1
2
nums = [1, 2, 3, 4]
print(calculate_sum(*nums)) # 输出: Received numbers: (1, 2, 3, 4) -> 10

关键字参数 (**kwargs)

当你希望函数能处理任意数量关键字参数时,可以使用这种类型。Python 会将这些参数收集到一个字典 (dict) 中。

可变参数处理的是位置参数,而关键字参数处理的是命名参数, 也就是说, 可变参数只能接收没有名称的参数, 而关键字参数接收的是有名称的参数.

实现方式是在参数名前加两个星号 。按照惯例,我们通常将其命名为 kwargs**。

1
2
3
4
5
6
7
8
9
10
11
12
def create_user_profile(**user_info):
"""创建一个用户信息的字典。"""
print(f"Received info: {user_info}") # user_info 是一个字典
for key, value in user_info.items():
print(f"{key.title()}: {value}")

create_user_profile(name="Eve", age=28, city="New York") # 需要输入 key=value 形式的参数
# 输出:
# Received info: {'name': 'Eve', 'age': 28, 'city': 'New York'}
# Name: Eve
# Age: 28
# City: New York

同样, 如果已经有一个字典, 想把它当作关键字参数传入函数, 可以使用 ** 号来解包:

1
2
3
4
5
6
7
user_data = {'name': 'Frank', 'age': 35, 'city': 'Los Angeles'}
create_user_profile(**user_data)
# 输出:
# Received info: {'name': 'Frank', 'age': 35, 'city': 'Los Angeles'}
# Name: Frank
# Age: 35
# City: Los Angeles
注意user_info获得的dict是user_data的一份拷贝,对user_info的改动不会影响到函数外的user_data。

命名关键字参数 (Named-Keyword Arguments)

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。有时候,我们希望函数能够接受一些特定的关键字参数,而不是任意的关键字参数。这时,可以使用命名关键字参数。

这种参数用于强制调用者必须使用制定关键字的形式来传递参数值,而不是通过位置。这可以提高代码的可读性,明确参数的意图。

也就是说, 必须以 key=value 的形式提供,不能通过位置传递。 - 如果前面有 args,则直接在 args 后面定义。 - 如果没有 args,则需要一个单独的星号 作为分隔符。

1
2
3
4
5
6
7
8
9
10
11
12
def process_data(initial_value, *data, status, is_valid):
"""处理数据,status 和 is_valid 必须用关键字指定。"""
print(f"Initial Value: {initial_value}")
print(f"Data tuple: {data}")
print(f"Status: {status}")
print(f"Is Valid: {is_valid}")

# 正确调用
process_data(100, 1, 2, 3, status="active", is_valid=True)

# 错误调用 - 未使用关键字
# process_data(100, 1, 2, 3, "active", True) # TypeError: process_data() takes 1 positional argument but 4 were given
1
2
3
4
5
6
7
8
9
10
11
def set_config(*, theme, font_size, show_toolbar):
"""配置设置,所有参数必须用关键字指定。"""
print(f"Theme: {theme}")
print(f"Font Size: {font_size}")
print(f"Show Toolbar: {show_toolbar}")

# 正确调用
set_config(theme="dark", font_size=14, show_toolbar=False)

# 错误调用 - 尝试使用位置参数
# set_config("dark", 14, False) # TypeError: set_config() takes 0 positional arguments but 3 were given

类型注解 (Type Annotations)

Python 允许在函数定义中使用类型注解来指定参数和返回值的预期类型。这有助于提高代码的可读性,并且可以被静态类型检查工具(如 mypy)使用来检测类型错误。

使用类型注解的语法是在参数名后使用冒号 :,然后跟上类型名称(如果参数有默认值, 则默认值放在类型名称后面, 用=连接):

对于返回值,可以在参数列表后使用箭头 -> 来指定返回类型。

1
2
3
4
def greet(name: str, age: int = 20) -> str:
"""返回一个问候语字符串。"""
return f"Hello, {name}. You are {age} years old."
print(greet("Alice", 30)) # 输出: Hello, Alice. You are 30 years old.

类型注解并不会影响函数的运行时行为,它们只是提供了额外的信息,帮助开发者理解代码的意图。

同时, 类型注解也可以用于变量声明:

1
2
age: int = 25
self.records: List[Dict[str, Any]] = [] # 表示 records 是一个包含字典的列表

高阶函数 (Higher-order Function)

要理解高阶函数,首先必须接受 Python 中的一个核心设计哲学:函数是“一等公民” (First-class Citizen)。

所谓“一等公民”,是指在 Python 中,函数可以像其他数据类型(如整数、字符串、列表等)一样被赋值给变量、作为参数传递给其他函数、作为函数的返回值返回。

一个函数如果满足以下两个条件中的至少一个,它就是高阶函数:

  • 接受一个或多个函数作为参数
  • 函数作为返回值

简单来说:一个操作其他函数的函数,就是高阶函数

接受函数作为参数

这是高阶函数最常见的形式。它允许我们将行为“注入”到一个函数中,使这个函数变得更加灵活和通用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 这是一个普通的函数
def say_hello(name):
return f"Hello, {name}"

def say_goodbye(name):
return f"Goodbye, {name}"

# 这就是一个高阶函数,因为它接受一个函数(fn)作为参数
def be_polite(fn):
# 在内部,它调用了传入的函数 fn
greeting = fn("John")
# 并在其基础上增加了一些行为
return greeting + ", have a nice day!"

# --- 使用高阶函数 ---
# 1. 把 say_hello 函数作为“值”传入 be_polite
polite_hello = be_polite(say_hello)
print(polite_hello) # 输出: Hello, John, have a nice day!

# 2. 把 say_goodbye 函数作为“值”传入 be_polite
polite_goodbye = be_polite(say_goodbye)
print(polite_goodbye) # 输出: Goodbye, John, have a nice day!

同时, Python 提供了很多非常有用的内置高阶函数,最典型的就是 map(), filter() 和 sorted()。

  • map(function, iterable): 对 iterable 中的每个元素执行 function 操作, 并把结果作为新的 Iterator 返回。

    1
    2
    3
    4
    5
    def 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]
    这里 map() 返回的不是一个“现成的列表”,而是一个惰性计算迭代器 (Iterator), 也就是说结果不会一次性全部算出来,而是按需计算. 当你对 r 进行迭代(比如 for x in r: 或 list(r))时,它才会逐个调用 f(x),生成结果。

  • filter(function, iterable): 使用 function 过滤 iterable 中的元素,只保留使 function 返回 True 的元素。

    1
    2
    3
    4
    5
    def 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
    2
    sorted([36, 5, -12, 9, -21], key=abs)
    # 输出: [5, 9, -12, -21, 36]

将函数作为返回值

高阶函数也可以动态地“创建”并返回一个新的函数。这通常与闭包 (Closure) 的概念紧密相关。

假设我们想创建一些功能专一的函数,比如“乘以2的函数”、“乘以3的函数”等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 这就是一个高阶函数,因为它返回一个函数
def create_multiplier(n):
# 这个内部函数 "multiplier" 就是将要被返回的
def multiplier(x):
return x * n # 注意:这里引用了外部函数的变量 n

return multiplier # 返回的是函数本身,不是调用的结果

# --- 使用高阶函数 ---
# 调用 create_multiplier(2) 并不执行乘法,而是创建了一个新的函数
# 这个新函数“记住”了 n=2
double = create_multiplier(2)

# 调用 create_multiplier(3) 创建了另一个新函数
# 这个新函数“记住”了 n=3
triple = create_multiplier(3)

# 现在我们可以使用这些新创建的函数了
print(f"double(10) is {double(10)}") # 输出: double(10) is 20
print(f"triple(10) is {triple(10)}") # 输出: triple(10) is 30
print(f"double(5) is {double(5)}") # 输出: double(5) is 10
在这个例子中,create_multiplier 是一个“函数工厂”。你给它一个参数 n,它就为你生产出一个“乘以n”的定制函数。返回的函数 multiplier 捕获并持有了外部环境中的变量 n,这就是一个闭包。

装饰器 (Decorators)

理解了高阶函数后,你就能理解 Python 中最强大的特性之一, 装饰器。

装饰器本质上就是一个高阶函数,它接受一个函数作为参数,并返回一个被“装饰”过的、功能更强的新函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import time

# a_decorator 本质上就是一个高阶函数
def a_decorator(func):
def wrapper(*args, **kwargs):

print(f"Calling function '{func.__name__}'...")
start_time = time.time()

result = func(*args, **kwargs) # 调用原始函数

end_time = time.time()
print(f"Function '{func.__name__}' took {end_time - start_time:.4f} seconds.")
return result
return wrapper

# @a_decorator 是 Python 的语法糖
# 它等价于: slow_function = a_decorator(slow_function)
@a_decorator
def slow_function():
time.sleep(1)
print("Function finished.")

slow_function()