当前位置:首页>python>Python 必知必会:编写 Python 生产级函数参数的 7 个 level,你用过几种?

Python 必知必会:编写 Python 生产级函数参数的 7 个 level,你用过几种?

  • 2026-09-02 19:56:25
Python 必知必会:编写 Python 生产级函数参数的 7 个 level,你用过几种?

0. 混沌模式

正如神话传说的那样,天地初开,一切都处于混沌模式。这是最基础、最原始的函数参数写法,也就是我们最初入门 Python 时,在基础教程中学到的——无类型位置参数。

def personal_info(name, gender, age):    ...

1. 默认参数

def greet(name, greeting='hello'):    print(greeting, name)greet('Jack')   # hello Jackgreet('Bob', greeting='hi') # hi Bob

此处的默认参数为:greeting="hello"

如果我们向 greeting 传递一个参数,它就会变成我们传递进去的参数。

如果您有很多具有默认值的参数,但仍然希望允许一定的自定义性,那么这种方法很好。

2. 类型提示(Type hinting)

def personal_info(    name: str, gender: str, age: int    ) -> dict[str, str]:    ...

类型提示的含义:

•
name 应为 str 类型
•
gender 应为 str 类型
•
age 应为 int 类型
•
该函数的返回值应为 dict[str, str] 类型(键为 str,值为 str 的字典)。

类型提示在生产代码中是必不可少的。没有类型提示,代码的可读性将会显著下降。

3. 类型别名、命名元组和类型化字典

当类型变得复杂时,类型提示也会变得复杂。试想一下:

def get_passing_and_failing_students(    students: list[dict[str, dict[str, int]]]    ) -> tuple[        list[dict[str, dict[str, int]]],        list[dict[str, dict[str, int]]],    ]:    ...

在这里,除非我们编写了这段代码接下来的逻辑,否则我们可能不知道 dict[str, dict[str, int]] 的具体含义。

使用类型别名使类型提示更清晰

我们可以将 dict[str, dict[str, int]] 赋值给 Student :

Student = dict[str, dict[str, int]]def get_passing_and_failing_students(    students: list[Student]    ) -> tuple[list[Student], list[Student]]:    ...

这里的 Student 是我们的别名,它使我们的类型提示更加清晰。

使用命名元组可以使类型提示更清晰

from typing import NamedTupleStudent = dict[str, dict[str, int]]class PassingFallingStudents(NamedTuple):    passing_students: list[Student]    falling_students: list[Student]def get_passing_and_failing_students(    students: list[Student]    ) -> PassingFallingStudents:    ...

这里使用命名元组告诉我们,第一个元素包含及格的学生,而第二个元素包含不及格的学生。

使用类型化字典使类型提示更清晰

由于 dict[str, dict[str, int]] 可能存在歧义,我们可以使用类型化的字典来澄清它。

from typing import NamedTuple, TypedDictclass SubjectToScoreMap(TypedDict):    math: int    science: intStudent = dict[str, SubjectToScoreMap]# eg. {'Bob': {'math': 90, 'science': 80}}class PassingFallingStudents(NamedTuple):    passing_students: list[Student]    falling_students: list[Student]def get_passing_and_failing_students(    students: list[Student]    ) -> PassingFallingStudents:    ...

即使需要编写更多代码行,我们也经常在生产代码中使用这种级别的类型提示,因为代码可读性是首要考虑因素。

4. 仅关键字参数

在具有许多参数的复杂函数中,过度使用位置参数可能会导致很多混乱。

为了提高可读性,我们可以强制使用关键字参数——这样用户只能使用关键字参数来传递内容到这些参数中。

这里, * 后面的所有参数都是关键字参数。如果尝试在这里传递位置参数,将会报错。

我这个功能非常棒,因为我坚信,当程序复杂度超过一定程度时,就不应该再使用位置参数了。

5. *args 和 **kwargs

*args 允许用户传入任意数量的位置参数

‘额外的’位置参数简单地存储在一个名为 args 的元组中。

**kwargs 允许用户传入任意数量的关键字参数

额外的关键字参数会简单地存储在一个名为 kwargs 字典中。

在生产环境中的应用

通常我们不会在普通函数中使用 *args 和 **kwargs,因为 *args 和 **kwargs 可能存在歧义,而生产代码审查人员最讨厌歧义。

相反,我们在编写通用函数(例如装饰器,即应用于多个其他函数的函数)时,通常使用 *args 和 **kwargs。

def log(func):    def _wrapper(*args, **kwargs):        out = func(*args, **kwargs)        print(f"{args=} {kwargs=} ==> {out}")        return out    return _wrapper

这里, log 装饰器旨在包装任何带有任何类型参数输入的函数。因此,最好在这里使用 *args 和 **kwargs。

6. 使用配置对象替换冗长的参数列表

当一个函数很复杂且参数过多时会发生什么?

def do_something(        algo1_alpha: float = 0.4,        algo1_beta: float = 1.9,        algo1_epsilon: float = 6,        algo1_random_state: int = 42,        algo2_alpha: float = 0.9,        algo2_beta: float = 1.5,        algo2_nobs: int = 1000,        algo3_alpha: float = 0.7,        algo3_epsilon: float = 0.7,        algo3_theta: float = 0.3) -> ...:    ...

如果内容有任何改动,阅读、编写和重构都会非常痛苦。

我们最好重构一下这段代码,并将这些参数分组到配置对象中。

from dataclasses import dataclass@dataclassclass Algo1Config:    alpha: float = 0.4    beta: float = 1.6    epsilon: float = 6    random_state: int = 42@dataclassclass Algo2Config:    alpha: float = 0.8    beta: float = 1.2    nobs: int = 1000@dataclassclass Algo3Config:    alpha: float = 0.2    epsilon: float = 0.7    theta: float = 0.9

我们的主函数会将这些配置对象作为参数接收。

def do_somenthing(        algo1_config = Algo1Config(),        algo2_config = Algo2Config(),        algo3_config = Algo3Config()) -> ...:    ...

注意我们的参数现在组织得更加有序了。

7. 通过参数进行依赖注入

函数创建自己的依赖项是一种常见的做法。

def get_students(min_math_score: int):    db = InitDatabase()    query = f"""    SELECT *    FROM students    WHERE math_score >= {min_math_score}    """    return db.query(query)

例如,在 get_students 中,它初始化了自己的数据库 db,这是一个依赖项。

使用依赖注入重写相同的函数

def get_students(db, min_math_score):    db = InitDatabase()    query = f"""    SELECT *    FROM students    WHERE math_score >= {min_math_score}    """    return db.query(query)

在这里,db (依赖项)作为参数传递给函数,而不是在函数本身中创建。

虽然这看起来似乎微不足道,但它也有一些优势。

•

get_students 不需要关心数据库是如何初始化的。

•

由于我们可以轻松地注入真实或虚假的数据库,因此为 get_students 编写单元测试变得更加容易。

如果您愿意支持我的写作,同时想提升自己的代码能力,特别是系统化、结构化、模块化的思维和解决实际问题的能力,可以订阅我的文章合集【Python 设计模式(Design Patterns)】

Thanks for your reading!

Enjoying coding, my friends! 🧑‍💻🧑‍💻🧑‍💻💯💯💯


推荐阅读👇👇👇

•
写给 Python 开发者的“内功心法”:23 种经典设计模式系列 (开篇)
•
Python 必知必会:10 个立即提升代码可读性的 Python 技巧!
•
Python 必知必会:初学者容易忽略(以后会后悔的)9 个 Python 概念!
•
Python 必知必会:停止使用 if x in list ——这里有更快捷的方法!
•
Python 必知必会:7 个我后悔忽略太久的 Python 特性!
•
2026年你还应该学习编程吗?

🌟 如果你觉得这篇文章对你有帮助,并且愿意支持我的话,你可以: 🌟

•
👍 点赞,让文章获得系统推荐
•
⤴️ 分享,把内容传递给身边的伙伴
•
❤️ 推荐,让文章影响到更多人
•
👏 欢迎留言交流,一起拓展技术的边界

👇👇👇 Follow me,获取更多高质量干货分享,我们下期再见!

最新文章

随机文章