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 自定义函数,了解了如何使用
def定义函数、通过参数向函数传递数据,以及使用return返回处理结果。但是,函数写好了,调用的时候还有讲究。
比如,我们编写了一个用于连接 Linux 服务器的函数,需要指定服务器地址、SSH 端口和连接超时时间。难道每次连接都必须把所有参数写一遍吗?如果函数有好几个参数,调用时又该如何让代码更容易阅读?
这篇文章,我们就来学习 Python 的位置参数、关键字参数和默认参数,让函数调用更加灵活,也为后续学习
*args和**kwargs打好基础。
先从一个简单的函数说起
假设我们正在编写一个用于生成问候语的函数:
1 | def greet(name, greeting): |
调用函数时,需要提供两个参数:
1 | greet("Alice", "Hello") |
运行结果:
1 | Hello, Alice |
这里有一个问题:调用 greet() 时,必须记住第一个参数是姓名,第二个参数是问候语。如果参数比较多,调用时就容易弄混。
有没有办法让调用代码更直观一些?
当然有!这就要从 Python 的参数传递方式说起。
位置参数:按照顺序传递数据
先看下面这段代码:
1 | def greet(name, greeting): |
输出
1 | Hello, Alice |
定义函数时,name 和 greeting 是形式参数。
调用函数时,"Alice" 和 "Hello" 是实际传入的数据。
当我们使用位置参数调用函数时,Python 会按照参数的位置进行匹配:
flowchart TD
A["调用 greet('Alice', 'Hello')"] --> B["第一个参数 Alice"]
A --> C["第二个参数 Hello"]
B --> D["name = Alice"]
C --> E["greeting = Hello"]
D --> F["执行函数体"]
E --> F
F --> G["输出 Hello, Alice"]位置参数的优点是简单直接,适合参数含义明确、数量不多的场景。
但如果参数比较多,只看调用代码可能就不那么容易理解了。
例如:
1 | def connect(host, port, timeout): |
输出
1 | 连接地址:192.168.1.10 |
看到 2222 和 30,我们可能需要回头查看函数定义,才能确定它们分别代表什么。
这时候,就可以使用关键字参数。
关键字参数:让调用代码更加清晰
关键字参数也常被称为命名参数。它的使用方法很简单:调用函数时,通过参数名明确指定对应的值。
例如:
1 | def greet(name, greeting): |
运行结果:
1 | Hello, Alice |
注意第二次调用:
1 | greet(greeting="Hi", name="Bob") |
虽然参数顺序与函数定义不同,但程序仍然能够正确执行。
这是因为关键字参数通过参数名称进行匹配,而不是依靠位置。
再回到连接 Linux 服务器的例子:
1 | def connect(host, port, timeout): |
输出
1 | 连接地址:192.168.1.10 |
这次即使不查看函数定义,也很容易看出每个参数的含义。
对于参数较多的函数,关键字参数通常能让调用代码更容易阅读和维护。
位置参数和关键字参数可以混合使用吗?
可以,但需要遵守规则。
位置参数必须放在关键字参数之前。例如:
1 | def greet(name, greeting): |
输出
1 | Hello, Alice |
这段代码是合法的。
但是,下面这种写法就不行:
1 | greet(name="Alice", "Hello") |
因为位置参数 "Hello" 出现在关键字参数之后,Python 会报 SyntaxError。
1 | File "<python-input-107>", line 1 |
记住一个简单的规则:混合使用时,先写位置参数,再写关键字参数。
默认参数:让函数调用更灵活
在 Linux 运维工作中,我们经常会遇到这样的情况:连接 SSH 服务时,大多数服务器使用默认的 22 端口,只有少数服务器修改过端口。
如果每次调用函数都必须填写端口号,就会产生很多重复代码。
能不能让函数提供一个默认端口,只有特殊情况下才手动指定?
当然可以,这就是默认参数。
看下面的例子:
1 | def connect(host, port=22, timeout=10): |
运行结果:
1 | 连接地址:192.168.1.10 |
定义函数时,我们给 port 设置了默认值 22,给 timeout 设置了默认值 10。
因此,调用 connect() 时,只需要提供必需的 host 参数,其他两个参数就可以省略。
如果遇到使用非标准 SSH 端口的服务器,也可以单独指定:
1 | connect("192.168.1.20", port=2222) |
运行结果:
1 | 连接地址:192.168.1.20 |
这里发生了什么?
host没有默认值,因此调用时必须提供。port有默认值22,但调用时传入了2222,所以使用传入的值。timeout有默认值10,调用时没有指定,因此继续使用默认值。
默认参数让函数既能应对常见场景,也能满足特殊需求。
默认参数必须放在什么位置?
Python 对默认参数的位置有一个要求:在普通函数定义中,有默认值的参数必须放在没有默认值的参数之后。
例如,下面的定义是正确的:
1 | def connect(host, port=22, timeout=10): |
而下面的定义会报 SyntaxError:
1 | def connect(port=22, host): |
输出
1 | File "<python-input-111>", line 1 |
因为 port 已经有默认值,后面的 host 却没有默认值。
如果确实需要这样的参数组合,就应该调整顺序:
1 | def connect(host, port=22): |
这样就符合 Python 的语法规则了。
默认参数需要注意的一个坑:可变对象
前面我们给参数设置的默认值都是数字,例如 22 和 10。
但是,如果默认值是列表、字典等可变对象,就需要格外小心。
看下面这个例子:
1 | def add_server(server, servers=[]): |
你可能会认为,每次调用 add_server() 时,servers 都会自动变成一个新的空列表。
但实际输出是:
1 | ['web-01'] |
为什么会这样?
因为 Python 在执行 def 语句、创建函数时,就会计算默认参数的值。后续调用函数时,如果没有传入 servers,就会继续使用同一个默认列表,而不是每次创建一个新列表。
于是,每次调用都会向同一个列表追加服务器名称。
在某些情况下,这种共享状态可能是有意设计的;但在一般业务代码中,它往往不是我们想要的结果。
更稳妥的写法是使用 None 作为默认值,在函数内部创建新列表:
1 | def add_server(server, servers=None): |
运行结果:
1 | ['web-01'] |
这样每次不传入 servers 时,函数都会创建一个新的列表,避免意外共享数据。
如果调用者主动传入一个已有列表,函数仍然会向这个列表追加数据:
1 | servers = ["web-01"] |
运行结果:
1 | ['web-01', 'web-02'] |
这里需要注意:传入的列表本身被修改了。如果不希望调用者传入的列表发生变化,还需要在函数内部复制列表,再进行操作。
综合实战:编写更灵活的服务器连接配置函数
前面我们分别学习了位置参数、关键字参数和默认参数。现在把它们组合起来,编写一个简单的服务器连接配置函数。
1 | def show_ssh_config(host, port=22, timeout=10): |
输出
1 | ============================== |
这段代码演示了三种不同的调用方式:
- 第一次调用:只提供服务器地址,其余参数使用默认值。
- 第二次调用:只修改端口,超时时间仍然使用默认值。
- 第三次调用:使用关键字参数明确指定所有参数。
在实际的运维自动化项目中,这种设计方式很常见。例如,封装 API 请求、数据库连接、SSH 连接配置时,都可以为常用参数设置合理的默认值,同时允许调用者根据需要覆盖它们。
当然,这个例子只是打印连接配置,并没有真正建立 SSH 连接。实际连接还需要使用相应的 SSH 库,并处理认证、超时和连接异常等问题。
写在最后
学到这里,我们已经掌握了 Python 函数的几种常见参数传递方式:
| 参数类型 | 主要特点 |
|---|---|
| 位置参数 | 按照参数位置传递值 |
| 关键字参数 | 通过参数名称指定值,调用更清晰 |
| 默认参数 | 为参数提供默认值,调用时可以省略 |
| 可变对象默认参数 | 需要注意默认对象可能被多次调用共享 |
实际编写函数时,可以根据场景灵活组合这些方式:必需的信息使用普通参数,常用配置提供默认值,参数较多时使用关键字参数提高可读性。
不过,还有一个问题:如果我们事先不知道调用者会传入多少个参数,该怎么办?
例如,我们想编写一个函数,一次接收任意数量的服务器名称;或者接收不同数量的配置项,又该如何处理?
下一篇,我们就来学习 Python 中的 *args 和 **kwargs,看看函数如何接收可变数量的参数。
