# Python 的 def：把反复抄的代码打包成一个名字

承接上一篇的字典：用 def 给一段反复要用的代码起名字，用参数把外面的数据按位置送进去，用 return 把结果交回来，并分清 print 只给人看、没写 return 会得到 None，让脚本靠“一处定义、处处调用”变短。

> 函数与参数 · return 与返回值 · 自动化入门 · 约 7 分钟 · 10 月 08 日

## 本篇要点

1. 用 def 把一段要反复用的代码存起来，并在一个名字下挂一份；定义本身不执行，名字后面加括号才是真正跑一遍。
2. 函数名后面括号里写的是参数，相当于预留的空位；调用时填进去的具体值按位置一一对应，位置传错通常不报错，只是结果错了。
3. 如果函数体里全是相乘，参数顺序传反了甚至得到同一个数，错得更隐蔽；像“原价减现价”这种函数顺序一换结果就相反。
4. return 把结果交回给调用它的那行代码，能存进变量、继续参与运算；没写 return 的函数返回 None。
5. print 只是把值显示给人看，程序本身拿不到，所以把 print 当 return 用会得到 None，继续运算还会报 TypeError。
6. 在函数体里用 = 造出的变量是局部的，出了函数就消失，报 NameError；进去靠参数，出来靠 return。
7. 函数让脚本变短的机制是同一件事只定义一次：表格从三个人变成三百个人代码长度不变，改规则只改函数里那一行。
8. def 必须写在调用之前，否则 Python 还没把名字挂上，会报 NameError。

---

上一篇把一行表格记录变成了字典，你已经能把一整张工资表装进程序里。但装进去之后会冒出另一个问题：**处理每一个人的那几行算式，还是一遍遍抄的**。这一篇解决的就是“同一段代码反复出现”——把它打包成一个有名字的东西，也就是函数。

## 先看看不打包会怎样

假设算加班费的规矩是：加班小时数 × 时薪 × 1.5 倍。公司只有三个人加班：

```python
时薪 = 50

print(f"张三：{2 * 时薪 * 1.5} 元")
print(f"李四：{4 * 时薪 * 1.5} 元")
print(f"王五：{3 * 时薪 * 1.5} 元")
```

三个人还能接受，可它埋了两个麻烦。

第一个麻烦是**改一处要改多处**。下个月公司把倍率从 1.5 调成 2，你得把每行的 `1.5` 都找出来改掉。三个人是三处，表格里三百个人就是三百处；只要漏掉一行，那个人的工资就悄悄算错了，而且不会报错。

第二个麻烦是这段算式**换不了地方用**。别的脚本也要算加班费，你只能整段复制过去，从此两份代码各自修改、慢慢分家。

真正要的其实很朴素：给这段算式**起个名字**，写一次，以后报名字就行。

## def：给一段代码起名字

Python 里起名字的关键字是 `def`，是 define（定义）的缩写：

```python
def 算加班费(小时, 时薪):
    倍数 = 1.5
    return 小时 * 时薪 * 倍数
```

一行行读：

- `def` 开头，告诉 Python“我要定义一个函数”。
- 紧跟着是**函数名**，你自己起，规则和变量名一样。这里叫 `算加班费`。
- 名字后面是**圆括号**，括号里写这个函数需要外面提供什么数据。这里要两样：加班多少小时、时薪多少。
- 行末的**冒号**，和你写 `if`、`for` 时一样，表示“下面缩进的那几行归属于它”。
- 下面**缩进的那几行**是这个函数的全部内容，叫函数体。前面 `if`、`for` 学过的缩进规则原样适用。

定义完之后，屏幕上**什么都不会发生**——这是零基础最容易愣住的地方。`def` 做的事只是把这几行代码存起来，并在 `算加班费` 这个名字下挂一份。它没有执行。

要它真的跑，得再写一行**调用**：

```python
print(算加班费(2, 50))   # 150.0
```

区别只在一对括号：`算加班费` 是那个名字本身，`算加班费(2, 50)` 才是“现在把这段代码跑一遍，拿 2 和 50 当输入”。名字后面带括号，才是干活。

## 参数：定义时留空位，调用时填数据

回头看定义和调用的对应关系：

```python
def 算加班费(小时, 时薪):     # 定义：留两个空位
    ...

算加班费(2, 50)              # 调用：往空位里填 2 和 50
```

定义时括号里写的 `小时`、`时薪`，叫**参数**（parameter）。它们本身不是数据，而是两个**预留的空位**，也可以理解成你在函数体里给“从外面进来的东西”预先起好了名字。调用时括号里填的 `2`、`50`，叫**实参**（argument），是真正送进去的值。

两边怎么对上？**按位置一一对应**：第 1 个值给第 1 个参数，第 2 个给第 2 个。所以 `2` 落到 `小时`，`50` 落到 `时薪`。

这条规矩意味着调用顺序不能随手换。看一个顺序真的会影响的例子：

```python
def 算差价(原价, 现价):
    return 原价 - 现价

print(算差价(100, 80))   # 20
print(算差价(80, 100))   # -20
```

同样两个数，只是换了位置，结果就从正变负。Python 不会替你判断哪个数“应该”是哪一项，它只认位置。更隐蔽的是，如果函数里全是相乘，传反了甚至得到同一个数（`2 × 50` 和 `50 × 2` 一样），你根本看不出错。

## return：把结果交回去

现在到了函数真正的关键一步。先看一个**看起来也能用**的版本：

```python
def 算加班费(小时, 时薪):
    print(小时 * 时薪 * 1.5)

结果 = 算加班费(2, 50)   # 屏幕上出现 150.0
print(结果)              # None
```

屏幕上明明打印了 150.0，可 `结果` 却是 `None`。原因在于 `print` 和 `return` 干的是两件不同的事：

- `print` 把值送到屏幕上，**只给人看**，程序本身拿不到这个值。
- `return` 把值**交回给那一行调用它的代码**，让程序能接着用。

Python 里每个函数都会交出一个值。你没写 `return`，它就默认交出 `None`（表示“没有值”）。所以上面那个函数“做了事，但没交出东西”，`结果` 自然是 `None`。要是接着写 `总计 = 8000 + 结果`，就会报 `TypeError`——等于拿数字去加一个空。

改成 `return` 就顺了。函数执行到 `return` 那一行，就把值交出去并立刻结束，`return` 后面的代码不会再跑：

```python
def 算加班费(小时, 时薪):
    return 小时 * 时薪 * 1.5

结果 = 算加班费(2, 50)
print(f"加班费 {结果} 元，本月总计 {8000 + 结果} 元")
```

这就是“返回值”的价值：它不只是显示出来，而是变成了一个**能继续参与计算、能存进变量、能传给别的函数**的东西。

## 打包之后，脚本为什么变短

把上一篇的字典列表和这一篇的函数接起来看，才能看清“变短”是什么意思：

```python
员工们 = [
    {"姓名": "张三", "加班小时": 2},
    {"姓名": "李四", "加班小时": 4},
    {"姓名": "王五", "加班小时": 3},
]

def 算加班费(小时, 时薪):
    return 小时 * 时薪 * 1.5

for 员工 in 员工们:
    金额 = 算加班费(员工["加班小时"], 50)
    print(f"{员工['姓名']}：{金额} 元")
```

注意几点：

- 循环体里看不到 `1.5` 了。算式被收进函数里，**只写了一次**。
- 循环每转一圈，就把这一行的加班小时数通过参数送进函数，函数把算好的金额交回来，装进 `金额`。
- 表格从三个人变成三百个人，代码长度**一点不变**；倍率从 1.5 改成 2，也只需要动 `return` 那一行。

这才是函数让脚本变短的机制：不是少打几个字，而是**把“同一件事”的定义只留下一处**。一处定义，处处调用，改规则只改那一处，不会漏。

## 两条边界，先记住

**一，函数里的变量外面看不见。** 上面 `倍数 = 1.5` 是在函数体里用 `=` 造出来的，它在函数内部存在，出了函数就没有了：

```python
print(倍数)
# NameError: name '倍数' is not defined
```

这不是缺陷，恰恰是打包的好处：函数内部自己用的临时名字不会和外面的名字打架，所以你可以放心复用。外面要用的东西，得写在参数里送进去；里面算出来的东西要让外面用，得 `return` 出来。这条边界也顺便解释了“打包”这个词——括号里进，`return` 出，中间的细节外面不必关心。

**二，`def` 要写在调用之前。** 脚本是从上往下执行的，`def` 是“把名字挂上去”的那一步；名字还没挂上就调用，会报 `NameError`。所以习惯上把函数定义写在文件靠上的位置，主流程放下面。

等以后参数多到三四个，Python 还有一种按名字传的写法（`算加班费(小时=2, 时薪=50)`），能避开位置顺序的麻烦，那是下一篇要讲的内容。

## 术语表

- 函数（function）：给一段完成某个小任务的代码起个名字存起来，之后报名字就能反复用。
- def：Python 里写函数定义的关键字，后面跟函数名和圆括号里的参数名单，冒号加缩进圈出函数体。
- 函数定义与函数调用：定义只是把代码挂到名字上、不执行；名字后面加括号才是让这段代码现在跑一遍。
- 参数与实参：定义时括号里的名字是预留的空位（参数），调用时填进去的具体值是实参，两边按位置一一对应。
- return 与返回值：把结果交回给调用它的那行代码；没写 return 的函数默认返回 None。
- 局部变量：在函数体里用 = 造出来的变量只存在于函数内部，外面看不见，也正因此不会和外面的名字打架。

## 来源

1. [Python 官方教程：定义函数（def 的写法、参数按位置传入、没有 return 的函数返回 None、return 会结束函数）](https://docs.python.org/zh-cn/3/tutorial/controlflow.html#defining-functions)

---

原文：https://pangzhengboyin.com/articles/python-def-functions-parameters-return-d1a3091c

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
