1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活

前面我们学习了 Python 中的 List、Tuple、Set、Dict 等数据结构,也掌握了如何使用 sort() 和 sorted() 整理数据。

但随着程序越来越复杂,你可能会发现一个新问题:同样的代码,为什么总要重复编写?

比如,在 Linux 运维工作中,我们经常需要输出服务器检查结果、格式化巡检信息,或者给日志内容添加统一的分隔线。如果每次都手动编写一遍相同的代码,不仅麻烦,还容易出现遗漏。

有没有办法把这些重复的操作封装起来,需要时直接调用呢?

这就是 Python 函数要解决的问题。

函数可以把一段具有特定功能的代码组织起来。我们只需要定义一次,就可以在程序的不同位置重复调用。

这一篇,我们从最基础的自定义函数开始,学习如何定义函数、传递参数、返回结果,以及如何为函数编写说明文档。

为什么需要自定义函数?

先看一个简单的例子。

假设我们希望输出一段文本,并在文本的上方和下方添加由 # 组成的分隔线。

如果不使用函数,代码可能是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
text = "Linux Server"

border = len(text) * "#"
print(border)
print(text)
print(border)

text = "Python Automation"

border = len(text) * "#"
print(border)
print(text)
print(border)

运行结果:

1
2
3
4
5
6
############
Linux Server
############
#################
Python Automation
#################

这段代码可以正常运行,但问题也很明显:相同的逻辑重复编写了两次。

如果以后还需要处理十段、几十段文本,是不是还要继续复制代码?

当然可以,但这并不是一个好办法。

我们真正需要的是把这段重复逻辑封装起来,以后只需要提供不同的文本,就可以完成相同的操作。

这时,就可以使用自定义函数。

定义第一个函数

在 Python 中,使用 def 关键字定义函数。

基本语法如下:

1
2
def 函数名称(参数):
函数体

这里有几个需要注意的地方:

  • def 是定义函数的关键字。
  • 函数名称用于标识这个函数,后续可以通过名称调用它。
  • 圆括号用于定义参数,没有参数时也需要保留圆括号。
  • 冒号表示函数定义头部结束。
  • 函数体需要缩进,通常使用四个空格。

例如,我们把前面的分隔线输出逻辑封装成一个函数:

1
2
3
4
5
def print_with_border(some_text):
border = len(some_text) * "#"
print(border)
print(some_text)
print(border)

这段代码定义了一个名为 print_with_border() 的函数。

其中,some_text 是函数的参数,用于接收需要处理的文本。

border 则根据文本长度生成相应的分隔线。

例如,文本长度为 5,就会生成五个 #;文本长度为 12,就会生成十二个 #。

需要注意,定义函数并不意味着函数体会立即执行。

执行上面的代码时,Python 会创建这个函数。只有当我们调用它时,函数体中的语句才会执行。

调用函数,让代码重复使用

函数定义完成后,就可以通过函数名称调用它。

1
2
3
4
5
6
7
8
9
10
def print_with_border(some_text):
border = len(some_text) * "#"
print(border)
print(some_text)
print(border)


print_with_border("Linux Server")
print_with_border("Python Automation")
print_with_border("Hello")

运行结果:

1
2
3
4
5
6
7
8
9
############
Linux Server
############
#################
Python Automation
#################
#####
Hello
#####

看到了吗?

我们只需要定义一次 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
2
def print_with_border(some_text):
...

其中的 some_text 就是一个参数。

参数可以理解为函数接收外部数据的入口。调用函数时,我们可以把不同的数据传递进来,让同一个函数处理不同的内容。

例如:

1
2
print_with_border("web-01")
print_with_border("db-01")

两次调用使用的是同一个函数,只是传入的数据不同。

在 Python 中,我们通常需要区分两个概念:

  • 形式参数(Parameter):函数定义时声明的参数,例如 some_text。
  • 实际参数(Argument):调用函数时传入的值,例如 "web-01"。

可以这样理解:

1
2
3
4
5
6
def greet(name):
print(f"Hello, {name}!")


greet("Alice")
greet("Bob")

运行结果:

1
2
Hello, Alice!
Hello, Bob!

这里,name 是形式参数,"Alice" 和 "Bob" 是两次调用时传入的实际参数。

函数并不需要提前知道具体会收到什么值。只要传入的数据符合函数的使用要求,同一个函数就可以处理不同的输入。

这也是函数比单纯复制代码更加灵活的原因。

使用 return 返回结果

前面的 print_with_border() 函数可以输出文本,但如果我们希望把处理后的文本保存起来,供其他代码继续使用,该怎么办?

这时就需要认识另一个重要关键字:return。

return 用于结束当前函数的执行,并将指定的结果返回给调用者。

先看一个例子:

1
2
3
4
5
6
7
def add(a, b):
return a + b


result = add(10, 20)

print(result)

运行结果:

1
30

调用 add(10, 20) 时,函数计算 a + b,然后通过 return 返回结果 30。

这个结果被赋值给变量 result,后续就可以继续使用。

例如:

1
2
3
4
5
6
7
8
def add(a, b):
return a + b


total = add(10, 20)

print("计算结果:", total)
print("再乘以 2:", total * 2)

运行结果:

1
2
计算结果: 30
再乘以 2: 60

这说明函数不仅可以执行操作,还可以将计算结果交给其他代码使用。

这是 Python 初学者经常遇到的问题。

简单来说:

  • print() 的作用是把内容输出到控制台。
  • return 的作用是把结果返回给调用者,方便后续代码继续处理。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
def add_with_print(a, b):
print(a + b)


def add_with_return(a, b):
return a + b


result1 = add_with_print(10, 20)
result2 = add_with_return(10, 20)

print("result1:", result1)
print("result2:", result2)

运行结果:

1
2
3
30
result1: None
result2: 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
2
3
4
5
6
7
8
9
10
11
12
def wrap_with_border(some_text):
border = len(some_text) * "#"
result = [border]
result.append(some_text)
result.append(border)

return "\n".join(result)


data = wrap_with_border("Linux Server")

print(data)

运行结果:

1
2
3
############
Linux Server
############

这里有几个值得注意的地方。

首先,result 是一个列表,用于保存分隔线、正文和底部分隔线。

然后,"\n".join(result) 会使用换行符把列表中的字符串连接起来,形成一个完整的字符串。

最后,return 将这个字符串返回给调用者。

这样一来,调用函数的代码就可以决定如何处理结果:

1
2
3
4
5
6
message = wrap_with_border("Linux Server")

print(message)

with open("server_report.txt", "w", encoding="utf-8") as file:
file.write(message + "\n")

输出

1
2
3
4
############
Linux Server
############
39

运行这段程序时,格式化后的文本会先输出到控制台,再写入 server_report.txt 文件。

这个例子说明,函数返回结果后,调用者可以根据需要选择输出、保存或者进一步处理。

当然,实际写入文件时还需要根据业务需要考虑文件覆盖、追加以及异常处理等问题。这里我们先关注函数返回值的基本用法。

函数可以返回什么?

return 并不局限于返回整数。

函数可以返回字符串、列表、字典、元组等各种 Python 对象。

例如,返回列表:

1
2
3
4
5
6
7
def get_servers():
return ["web-01", "web-02", "db-01"]


servers = get_servers()

print(servers)

运行结果:

1
['web-01', 'web-02', 'db-01']

也可以返回字典:

1
2
3
4
5
6
7
8
9
10
11
12
def get_server_info():
return {
"name": "web-01",
"cpu_usage": 82,
"status": "running"
}


server = get_server_info()

print(server["name"])
print(server["cpu_usage"])

运行结果:

1
2
web-01
82

这与前面学习的 List 和 Dict 联系了起来。

我们可以让函数负责生成数据,再让其他代码负责遍历、排序、筛选或者输出。

需要注意,return 执行后,当前函数就会结束。写在该 return 语句之后、同一执行路径上的代码不会继续执行。

例如:

1
2
3
4
5
6
def check_server():
return "检查完成"
print("这行代码不会执行")


print(check_server())

运行结果:

1
检查完成

因此,编写函数时,要注意 return 的位置,避免把需要执行的代码放在无法到达的语句之后。

为函数编写文档字符串

函数写好了,自己能看懂就够了吗?

如果程序越来越复杂,或者以后需要维护别人编写的代码,仅仅依靠函数名称和实现细节,可能并不足以快速理解它的用途。

这时,可以使用文档字符串(Docstring)为函数编写说明。

文档字符串是放在函数体开头的字符串,用于说明函数的用途、参数和返回值等信息。

例如:

1
2
3
4
5
6
def wrap_with_border(some_text):
"""返回添加上下分隔线后的文本。"""
border = len(some_text) * "#"
result = [border, some_text, border]

return "\n".join(result)

这里的三引号字符串就是文档字符串。

它位于函数体的第一条语句,因此 Python 会将它识别为这个函数的文档。

我们可以通过函数的 __doc__ 属性查看文档字符串:

1
print(wrap_with_border.__doc__)

运行结果:

1
返回添加上下分隔线后的文本。

也可以使用内置函数 help() 查看函数说明:

1
help(wrap_with_border)

输出

1
2
3
4
Help on function wrap_with_border in module __main__:

wrap_with_border(some_text)
返回添加上下分隔线后的文本。

输出中会包含函数名称、模块信息以及文档字符串等内容,具体格式可能因运行环境而略有不同。

文档字符串应该写什么?

对于简单函数,一句话说明用途就已经很有帮助。

对于更复杂的函数,可以进一步说明:

  • 函数用来解决什么问题。
  • 参数需要传入什么数据。
  • 函数会返回什么结果。
  • 是否存在需要注意的使用限制。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def calculate_average(numbers):
"""计算一组数字的平均值。

Args:
numbers: 参与计算的数字列表,不能为空。

Returns:
这组数字的平均值。

Raises:
ValueError: numbers 为空时抛出异常。
"""
if not numbers:
raise ValueError("numbers 不能为空")

return sum(numbers) / len(numbers)

这个例子不仅展示了文档字符串,也通过 if 和 raise 演示了如何明确处理空列表的情况。

注意,文档字符串只是说明文档,本身不会自动检查参数是否符合要求。真正的参数校验和异常处理,仍然需要通过代码实现。

实战:封装服务器信息输出函数

前面我们学习了函数定义、参数、返回值和文档字符串,现在把它们组合起来,完成一个简单的 Linux 运维场景示例。

假设我们有多台服务器,需要统一输出服务器名称和 CPU 使用率。

如果每次输出都单独编写格式化代码,程序会逐渐变得重复而难以维护。

我们可以把输出格式封装成一个函数:

1
2
3
4
5
6
7
8
def format_server_status(name, cpu_usage):
"""格式化服务器名称和 CPU 使用率。"""
return f"服务器: {name}, CPU 使用率: {cpu_usage}%"


print(format_server_status("web-01", 35))
print(format_server_status("web-02", 92))
print(format_server_status("db-01", 85))

运行结果:

1
2
3
服务器: web-01, CPU 使用率: 35%
服务器: web-02, CPU 使用率: 92%
服务器: db-01, CPU 使用率: 85%

现在,输出格式只需要维护一份。

如果以后希望把输出内容修改成 JSON 风格,或者增加磁盘使用率字段,就可以集中修改函数,而不必在程序的每个位置分别调整。

当然,这里的 CPU 使用率是示例数据,并不是程序实时采集的结果。实际工作中,我们可以先通过系统命令、监控平台或 API 获取数据,再交给函数进行格式化处理。

通过这种方式,函数就能成为连接数据采集、数据处理和结果输出的一个小模块。

本篇知识速查

概念作用
def定义函数
函数名称用于标识和调用函数
形式参数在函数定义中声明的参数
实际参数调用函数时传入的值
return结束当前函数并返回结果
None函数没有显式返回其他值时的默认返回值
print()将内容输出到控制台
文档字符串说明函数用途、参数和返回值等信息
help()查看函数帮助信息
__doc__访问函数的文档字符串

写在最后

这一篇,我们学习了 Python 自定义函数的基础知识。

从使用 def 定义函数,到通过参数接收数据,再到使用 return 返回结果,最后为函数编写文档字符串,我们已经能够把一段重复的代码封装成可以重复使用的功能模块。

在 Linux 运维自动化中,函数非常实用。无论是格式化巡检报告、处理日志数据,还是封装服务器检查逻辑,都可以通过函数让代码更加清晰、易于复用和维护。

不过,目前我们使用的函数还比较简单。比如,调用函数时必须按照参数定义提供相应的数据。如果有些参数希望使用默认值,或者希望通过参数名称指定传入的内容,又应该怎么做?

下一篇,我们继续学习 命名参数与可选参数,让函数的调用更加灵活。