# Python 函数：def、return、参数、*args 与 **kwargs、lambda 和类型注解

URL: https://skillaria.top/zh/guides/python/funkcii-python

函数是 Python 中有名字的代码块，用 def 定义，通过名字加括号调用，并用 return 返回结果；没有 return 时函数返回 None。参数可以按位置或按名字传递，*args 把多余的位置参数收集为元组，**kwargs 把多余的关键字参数收集为字典，而 lambda 用来创建只含一个表达式的简短匿名函数。

Python 中的函数是一段有名字的代码，可以用不同的输入数据反复调用。用 `def` 声明它，通过参数接收数据，并通过 `return` 返回结果。函数是把冗长的脚本拆分成清晰、可检验步骤的主要手段。

## 如何定义函数：def

函数声明以 `def` 开头，后面是名字、括号中的参数和冒号。函数体要缩进四个空格——缩进在 Python 中是语法的一部分，而不是装饰。

```
def greet(name):
    """返回给用户的问候语。"""
    return f"你好，{name}！"

print(greet("安娜"))   # 你好，安娜！
```

紧跟在函数头后面的三引号字符串是 docstring，即函数的文档。`help(greet)` 和编辑器的提示都会显示它。根据 PEP 8 的约定，函数名使用 snake_case 风格：`add_expense`、`get_rate`。

`def greet(name):` 这一行被称为函数签名：名字加上参数列表（如果有注解，还包括注解）。读者只看签名就能知道如何调用函数，无需阅读函数体。

## return 在 Python 中的作用

`return` 结束函数，并把值交给调用者。执行 `return` 之后的代码不会再运行。如果没有 `return`，或者 `return` 后面没有值，函数返回 `None`。

```
def area(w, h):
    w * h          # 结果被计算出来，然后丢弃

print(area(3, 4))  # None

def area(w, h):
    return w * h

print(area(3, 4))  # 12
```

忘记写 `return` 是新手最常见的错误之一：函数“能运行”，却返回 `None`，错误会在下一行才显现出来。函数也可以返回多个值——实际上它返回的是一个元组，可以方便地解包：

```
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([4, 1, 9])
print(low, high)   # 1 9
```

提前使用 `return` 可以减少多余的嵌套：先处理特殊情况，再写主要逻辑。

```
def discount(price, is_member):
    if price  limit    # 读取全局变量——可以

def raise_limit():
    global limit
    limit = 2000             # 没有 global 的话，会创建一个局部变量
```

实践中最好避免使用 `global`：修改外部状态的函数很难测试。更可靠的方式是通过参数接收值，并返回结果。

## 嵌套函数与闭包

函数可以定义在另一个函数内部。内层函数即使在外层函数结束之后，仍然可以访问外层的变量——这就是闭包。要修改这样的变量，需要使用 `nonlocal`。

```
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c(), c(), c())   # 1 2 3
```

闭包是装饰器和函数工厂的基础。

## 函数也是对象

在 Python 中，函数是普通的对象：可以把它赋给变量、作为参数传给另一个函数、从函数中返回，也可以存放在字典中。函数还拥有一些属性：`__name__`、`__doc__`、`__defaults__`、`__annotations__`。

```
def double(x):
    return x * 2

f = double
print(f(5), f.__name__)                # 10 double
print(list(map(double, [1, 2, 3])))   # [2, 4, 6]
```

## Python 中的 lambda：匿名函数

lambda 函数是没有名字的简短函数，函数体只能是一个表达式。表达式的结果会被自动返回，不能写 `return`。

```
square = lambda x: x ** 2
print(square(4))   # 16

expenses = [("咖啡", 250), ("打车", 600), ("午饭", 450)]
expenses.sort(key=lambda e: e[1], reverse=True)
print(expenses)  # [('打车', 600), ('午饭', 450), ('咖啡', 250)]

print(list(filter(lambda n: n % 2 == 0, range(10))))  # [0, 2, 4, 6, 8]
```

lambda 适合作为 `sorted`、`max`、`map`、`filter` 的简短参数。但 PEP 8 不推荐像上面的 `square` 那样把 lambda 赋给变量：如果函数需要名字，就用 `def`——这样它在 traceback 中有正常的 `__name__`，并且可以写 docstring。

|  | def | lambda |
| --- | --- | --- |

| 名字 | 有 | 没有（显示为 `<lambda>`） |

| 函数体 | 任意数量的语句 | 一个表达式 |

| return | 显式 | 隐式 |

| Docstring、注解 | 有 | 没有 |

| 适用场景 | 任何情况 | 简短的 key 或回调 |

## 类型注解：提示，而不是检查

注解描述参数和返回值的预期类型。Python 运行时不会检查它们：带有 `x: int` 的函数仍然会接受字符串。检查注解的是外部工具——mypy、pyright、ty——以及在运行之前就能标出错误的编辑器。

```
def add_expense(title: str, amount: float, category: str = "其他") -> dict[str, object]:
    return {"title": title, "amount": amount, "category": category}

def area(w: float, h: float) -> float:
    w * h   # mypy: Missing return statement
```

不安装即可运行检查：`uvx mypy .`。上面的例子展示了注解的价值：在 `-> float` 的函数中忘记 `return`，分析器会立即发现。

自 Python 3.12 起（PEP 695），泛型函数可以不用 `TypeVar` 来编写：类型参数写在方括号中，类型别名用关键字 `type` 声明。

```
def first[T](xs: list[T]) -> T:
    return xs[0]

type Money = int | float
```

在 Python 3.14 中（PEP 649），注解变成了惰性求值：它们在第一次访问时才会计算，因此可以不加引号引用后面才定义的类，也不再需要 `from __future__ import annotations`。

## “没有传参”还是“传了 None”：3.15 中的 sentinel

把默认值设为 `None` 时，无法区分“没有传参”和“明确传入了 None”。多年来人们用自定义的占位符解决这个问题：

```
_MISSING = object()

def get_rate(currency, default=_MISSING):
    rates = {"USD": 90.5}
    if currency in rates:
        return rates[currency]
    if default is _MISSING:
        raise KeyError(currency)
    return default
```

Python 3.15 新增了内置函数 `sentinel`（PEP 661）：`MISSING = sentinel("MISSING")` 创建一个唯一的标记，它有清晰的 repr，复制后依然是它本身，可以用 pickle 序列化，并且可以用在 `float | MISSING` 这样的注解中。在旧版本中，用 `object()` 的方法仍然有效。

## 函数中的常见错误

- **忘记 return** — 函数返回 `None`。
- **忘记括号** — `print(greet)` 输出的是函数对象，而不是调用的结果。
- **可变的默认值** — 所有调用共享同一个列表。
- **参数数量错误** — `TypeError: greet() missing 1 required positional argument: 'name'`。
- **名字与内置函数相同** — 自定义的 `list` 或 `sum` 会遮盖内置函数。

## 小结

Python 函数用 `def` 定义，通过 `return` 返回结果，参数可以按顺序或按名字传递。`*args` 和 `**kwargs` 提供灵活性，`/` 和 `*` 保证调用的严格性，lambda 适合一次性的简短函数，而类型注解配合 mypy 能在运行前发现错误。课程中这一主题同样结合实践讲解：支出记录器被拆分为带注解的函数，并用 mypy 检查。

## FAQ

**形参和实参有什么区别？**

形参是函数声明中的名字，实参是调用时传入的具体值。在 def greet(name) 中，name 是形参；在 greet("安娜") 中，字符串 "安娜" 是实参。

**能否在函数声明之前调用它？**

在模块层级不行：def 语句必须先执行，否则会出现 NameError。但在另一个函数内部，只要调用发生在声明之后，就可以调用后面定义的函数。

**一个函数可以返回多个值吗？**

从形式上看只返回一个值，但这个值可以是元组：return a, b 返回一个含两个元素的元组，可以用 x, y = f() 解包。

**必须写类型注解吗？**

不必要，Python 没有它们也能运行。但在供他人阅读的代码中，注解大大方便维护，并且能让 mypy 或 pyright 在运行前发现错误。

**lambda 中可以使用 if 吗？**

可以，但只能是条件表达式：lambda x: "偶" if x % 2 == 0 else "奇"。lambda 中不允许使用语句、循环和赋值。


---
Source: Skillaria.Top (https://skillaria.top)
