Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
前面我们学习了 Python 中的 List、Tuple、Set、Dict 等数据结构,也掌握了如何使用
sort()和sorted()整理数据。但随着程序越来越复杂,你可能会发现一个新问题:同样的代码,为什么总要重复编写?
比如,在 Linux 运维工作中,我们经常需要输出服务器检查结果、格式化巡检信息,或者给日志内容添加统一的分隔线。如果每次都手动编写一遍相同的代码,不仅麻烦,还容易出现遗漏。
有没有办法把这些重复的操作封装起来,需要时直接调用呢?
这就是 Python 函数要解决的问题。
函数可以把一段具有特定功能的代码组织起来。我们只需要定义一次,就可以在程序的不同位置重复调用。
这一篇,我们从最基础的自定义函数开始,学习如何定义函数、传递参数、返回结果,以及如何为函数编写说明文档。
为什么需要自定义函数?
先看一个简单的例子。
假设我们希望输出一段文本,并在文本的上方和下方添加由 # 组成的分隔线。
如果不使用函数,代码可能是这样的:
1 | text = "Linux Server" |
运行结果:
1 | ############ |
这段代码可以正常运行,但问题也很明显:相同的逻辑重复编写了两次。
如果以后还需要处理十段、几十段文本,是不是还要继续复制代码?
当然可以,但这并不是一个好办法。
我们真正需要的是把这段重复逻辑封装起来,以后只需要提供不同的文本,就可以完成相同的操作。
这时,就可以使用自定义函数。
定义第一个函数
在 Python 中,使用 def 关键字定义函数。
基本语法如下:
1 | def 函数名称(参数): |
这里有几个需要注意的地方:
def是定义函数的关键字。- 函数名称用于标识这个函数,后续可以通过名称调用它。
- 圆括号用于定义参数,没有参数时也需要保留圆括号。
- 冒号表示函数定义头部结束。
- 函数体需要缩进,通常使用四个空格。
例如,我们把前面的分隔线输出逻辑封装成一个函数:
1 | def print_with_border(some_text): |
这段代码定义了一个名为 print_with_border() 的函数。
其中,some_text 是函数的参数,用于接收需要处理的文本。
border 则根据文本长度生成相应的分隔线。
例如,文本长度为 5,就会生成五个 #;文本长度为 12,就会生成十二个 #。
需要注意,定义函数并不意味着函数体会立即执行。
执行上面的代码时,Python 会创建这个函数。只有当我们调用它时,函数体中的语句才会执行。
调用函数,让代码重复使用
函数定义完成后,就可以通过函数名称调用它。
1 | def print_with_border(some_text): |
运行结果:
1 | ############ |
看到了吗?
我们只需要定义一次 print_with_border(),就可以传入不同的文本,多次调用这个函数。
以后如果想修改分隔线的生成方式,只需要修改函数内部的实现,而不必在程序的每个位置寻找重复代码。
这就是函数最重要的价值之一:封装重复逻辑,提高代码的复用性和可维护性。
函数的定义与调用,可以简单理解为下面这个过程:
flowchart TD
A["定义函数 def"] --> B["调用函数"]
B --> C["传入实际参数"]
C --> D["执行函数体"]
D --> E{"是否使用 return?"}
E -->|是| F["返回结果给调用者"]
E -->|否| G["默认返回 None"]
F --> H["继续执行后续代码"]
G --> H
classDef define fill:#e3f2fd,stroke:#1976d2,color:#1565c0
classDef process fill:#e8f5e9,stroke:#388e3c,color:#2e7d32
classDef decision fill:#fff3e0,stroke:#f57c00,color:#e65100
class A define
class B,C,D,F,G,H process
class E decision这里还要注意一个细节:函数必须先定义,才能在执行过程中被调用。
因此,通常需要先编写函数定义,再编写调用它的代码。
参数与实际传入的值
前面的例子中,函数定义如下:
1 | def print_with_border(some_text): |
其中的 some_text 就是一个参数。
参数可以理解为函数接收外部数据的入口。调用函数时,我们可以把不同的数据传递进来,让同一个函数处理不同的内容。
例如:
1 | print_with_border("web-01") |
两次调用使用的是同一个函数,只是传入的数据不同。
在 Python 中,我们通常需要区分两个概念:
- 形式参数(Parameter):函数定义时声明的参数,例如
some_text。 - 实际参数(Argument):调用函数时传入的值,例如
"web-01"。
可以这样理解:
1 | def greet(name): |
运行结果:
1 | Hello, Alice! |
这里,name 是形式参数,"Alice" 和 "Bob" 是两次调用时传入的实际参数。
函数并不需要提前知道具体会收到什么值。只要传入的数据符合函数的使用要求,同一个函数就可以处理不同的输入。
这也是函数比单纯复制代码更加灵活的原因。
使用 return 返回结果
前面的 print_with_border() 函数可以输出文本,但如果我们希望把处理后的文本保存起来,供其他代码继续使用,该怎么办?
这时就需要认识另一个重要关键字:return。
return 用于结束当前函数的执行,并将指定的结果返回给调用者。
先看一个例子:
1 | def add(a, b): |
运行结果:
1 | 30 |
调用 add(10, 20) 时,函数计算 a + b,然后通过 return 返回结果 30。
这个结果被赋值给变量 result,后续就可以继续使用。
例如:
1 | def add(a, b): |
运行结果:
1 | 计算结果: 30 |
这说明函数不仅可以执行操作,还可以将计算结果交给其他代码使用。
print() 和 return 有什么区别?
这是 Python 初学者经常遇到的问题。
简单来说:
print()的作用是把内容输出到控制台。return的作用是把结果返回给调用者,方便后续代码继续处理。
例如:
1 | def add_with_print(a, b): |
运行结果:
1 | 30 |
为什么 result1 是 None?
因为 add_with_print() 只是输出了计算结果,并没有使用 return 返回这个值。
在 Python 中,如果函数执行结束时没有显式返回其他值,就会默认返回 None。
因此,虽然 add_with_print(10, 20) 输出了 30,但它的返回值仍然是 None。
而 add_with_return() 返回了计算结果 30,所以 result2 得到了这个值。
在实际开发中,如果函数的目的是计算数据、处理文本或者生成结果,通常应该考虑使用 return,而不是只使用 print()。
使用 return 封装文本处理逻辑
现在,我们把前面的分隔线示例改进一下。
之前的 print_with_border() 负责直接输出文本。如果希望把格式化后的文本交给其他代码使用,可以将它改写成返回字符串的函数:
1 | def wrap_with_border(some_text): |
运行结果:
1 | ############ |
这里有几个值得注意的地方。
首先,result 是一个列表,用于保存分隔线、正文和底部分隔线。
然后,"\n".join(result) 会使用换行符把列表中的字符串连接起来,形成一个完整的字符串。
最后,return 将这个字符串返回给调用者。
这样一来,调用函数的代码就可以决定如何处理结果:
1 | message = wrap_with_border("Linux Server") |
输出
1 | ############ |
运行这段程序时,格式化后的文本会先输出到控制台,再写入 server_report.txt 文件。
这个例子说明,函数返回结果后,调用者可以根据需要选择输出、保存或者进一步处理。
当然,实际写入文件时还需要根据业务需要考虑文件覆盖、追加以及异常处理等问题。这里我们先关注函数返回值的基本用法。
函数可以返回什么?
return 并不局限于返回整数。
函数可以返回字符串、列表、字典、元组等各种 Python 对象。
例如,返回列表:
1 | def get_servers(): |
运行结果:
1 | ['web-01', 'web-02', 'db-01'] |
也可以返回字典:
1 | def get_server_info(): |
运行结果:
1 | web-01 |
这与前面学习的 List 和 Dict 联系了起来。
我们可以让函数负责生成数据,再让其他代码负责遍历、排序、筛选或者输出。
需要注意,return 执行后,当前函数就会结束。写在该 return 语句之后、同一执行路径上的代码不会继续执行。
例如:
1 | def check_server(): |
运行结果:
1 | 检查完成 |
因此,编写函数时,要注意 return 的位置,避免把需要执行的代码放在无法到达的语句之后。
为函数编写文档字符串
函数写好了,自己能看懂就够了吗?
如果程序越来越复杂,或者以后需要维护别人编写的代码,仅仅依靠函数名称和实现细节,可能并不足以快速理解它的用途。
这时,可以使用文档字符串(Docstring)为函数编写说明。
文档字符串是放在函数体开头的字符串,用于说明函数的用途、参数和返回值等信息。
例如:
1 | def wrap_with_border(some_text): |
这里的三引号字符串就是文档字符串。
它位于函数体的第一条语句,因此 Python 会将它识别为这个函数的文档。
我们可以通过函数的 __doc__ 属性查看文档字符串:
1 | print(wrap_with_border.__doc__) |
运行结果:
1 | 返回添加上下分隔线后的文本。 |
也可以使用内置函数 help() 查看函数说明:
1 | help(wrap_with_border) |
输出
1 | Help on function wrap_with_border in module __main__: |
输出中会包含函数名称、模块信息以及文档字符串等内容,具体格式可能因运行环境而略有不同。
文档字符串应该写什么?
对于简单函数,一句话说明用途就已经很有帮助。
对于更复杂的函数,可以进一步说明:
- 函数用来解决什么问题。
- 参数需要传入什么数据。
- 函数会返回什么结果。
- 是否存在需要注意的使用限制。
例如:
1 | def calculate_average(numbers): |
这个例子不仅展示了文档字符串,也通过 if 和 raise 演示了如何明确处理空列表的情况。
注意,文档字符串只是说明文档,本身不会自动检查参数是否符合要求。真正的参数校验和异常处理,仍然需要通过代码实现。
实战:封装服务器信息输出函数
前面我们学习了函数定义、参数、返回值和文档字符串,现在把它们组合起来,完成一个简单的 Linux 运维场景示例。
假设我们有多台服务器,需要统一输出服务器名称和 CPU 使用率。
如果每次输出都单独编写格式化代码,程序会逐渐变得重复而难以维护。
我们可以把输出格式封装成一个函数:
1 | def format_server_status(name, cpu_usage): |
运行结果:
1 | 服务器: web-01, CPU 使用率: 35% |
现在,输出格式只需要维护一份。
如果以后希望把输出内容修改成 JSON 风格,或者增加磁盘使用率字段,就可以集中修改函数,而不必在程序的每个位置分别调整。
当然,这里的 CPU 使用率是示例数据,并不是程序实时采集的结果。实际工作中,我们可以先通过系统命令、监控平台或 API 获取数据,再交给函数进行格式化处理。
通过这种方式,函数就能成为连接数据采集、数据处理和结果输出的一个小模块。
本篇知识速查
| 概念 | 作用 |
|---|---|
def | 定义函数 |
| 函数名称 | 用于标识和调用函数 |
| 形式参数 | 在函数定义中声明的参数 |
| 实际参数 | 调用函数时传入的值 |
return | 结束当前函数并返回结果 |
None | 函数没有显式返回其他值时的默认返回值 |
print() | 将内容输出到控制台 |
| 文档字符串 | 说明函数用途、参数和返回值等信息 |
help() | 查看函数帮助信息 |
__doc__ | 访问函数的文档字符串 |
写在最后
这一篇,我们学习了 Python 自定义函数的基础知识。
从使用 def 定义函数,到通过参数接收数据,再到使用 return 返回结果,最后为函数编写文档字符串,我们已经能够把一段重复的代码封装成可以重复使用的功能模块。
在 Linux 运维自动化中,函数非常实用。无论是格式化巡检报告、处理日志数据,还是封装服务器检查逻辑,都可以通过函数让代码更加清晰、易于复用和维护。
不过,目前我们使用的函数还比较简单。比如,调用函数时必须按照参数定义提供相应的数据。如果有些参数希望使用默认值,或者希望通过参数名称指定传入的内容,又应该怎么做?
下一篇,我们继续学习 命名参数与可选参数,让函数的调用更加灵活。
