
正如神话传说的那样,天地初开,一切都处于混沌模式。这是最基础、最原始的函数参数写法,也就是我们最初入门 Python 时,在基础教程中学到的——无类型位置参数。
def personal_info(name, gender, age): ...def greet(name, greeting='hello'): print(greeting, name)greet('Jack') # hello Jackgreet('Bob', greeting='hi') # hi Bob此处的默认参数为:
greeting="hello"
如果我们向 greeting 传递一个参数,它就会变成我们传递进去的参数。
如果您有很多具有默认值的参数,但仍然希望允许一定的自定义性,那么这种方法很好。
def personal_info( name: str, gender: str, age: int ) -> dict[str, str]: ...类型提示的含义:
name 应为 str 类型gender 应为 str 类型age 应为 int 类型dict[str, str] 类型(键为 str,值为 str 的字典)。类型提示在生产代码中是必不可少的。没有类型提示,代码的可读性将会显著下降。
当类型变得复杂时,类型提示也会变得复杂。试想一下:
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: ...即使需要编写更多代码行,我们也经常在生产代码中使用这种级别的类型提示,因为代码可读性是首要考虑因素。
在具有许多参数的复杂函数中,过度使用位置参数可能会导致很多混乱。
为了提高可读性,我们可以强制使用关键字参数——这样用户只能使用关键字参数来传递内容到这些参数中。

这里, * 后面的所有参数都是关键字参数。如果尝试在这里传递位置参数,将会报错。
我这个功能非常棒,因为我坚信,当程序复杂度超过一定程度时,就不应该再使用位置参数了。
*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。

当一个函数很复杂且参数过多时会发生什么?
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()) -> ...: ...注意我们的参数现在组织得更加有序了。
函数创建自己的依赖项是一种常见的做法。
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! 🧑💻🧑💻🧑💻💯💯💯
推荐阅读👇👇👇
🌟 如果你觉得这篇文章对你有帮助,并且愿意支持我的话,你可以: 🌟
• 👍 点赞,让文章获得系统推荐 • ⤴️ 分享,把内容传递给身边的伙伴 • ❤️ 推荐,让文章影响到更多人 • 👏 欢迎留言交流,一起拓展技术的边界

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