跳到主要内容

Python 函数机制

函数把一组指令组织起来,调用时可以传入数据。函数定义中的输入名称叫形参,调用时提供的值叫实参。函数执行后返回结果,或在出错时抛出异常。下面的 clamp 把数值限制在 lowerupper 的区间内,例如 clamp(12, 0, 10) 返回 10;传入 strict=True 后,越界值会触发异常,不再调整到边界值。value: float 这样的注解说明预期的输入类型,-> float 说明预期的返回类型,但不会在运行时强制检查类型。

def clamp(value: float, lower: float, upper: float, *, strict: bool = False) -> float:
if lower > upper:
raise ValueError("lower must not exceed upper")
if strict and not lower <= value <= upper:
raise ValueError("value is outside the interval")
return min(max(value, lower), upper)

注意代码中的 *,它强制后续参数必须使用关键字传递(keyword-only)。对于 strict 这类行为开关,如果允许按位置传参,调用方很容易搞混参数含义,强制关键字能显著提升代码可读性。

参数类型

Python 支持多种参数形式:

  • / 之前的仅位置参数(Positional-only);
  • 默认的位置或关键字参数
  • *args 接收可变位置参数
  • * 之后的仅关键字参数(Keyword-only);
  • **kwargs 接收可变关键字参数

设计接口时,应遵循“最小必要原则”,只暴露意图清晰的参数。随意转发 *args**kwargs 会削弱接口的自解释性,增加维护成本。

默认值的求值时机

这是一个经典陷阱:默认值表达式仅在函数定义时执行一次,而非每次调用时执行。因此,除非确实需要跨调用共享状态,否则应避免用可变对象(如列表、字典)作为默认值。

def collect(item: str, bucket: list[str] | None = None) -> list[str]:
if bucket is None:
bucket = []
bucket.append(item)
return bucket

上述写法用 None 作为哨兵值:省略 bucket 或传入 None 时创建新列表;传入已有列表时则修改并返回该列表。

返回值与所有权

  • 若函数执行到底部未遇到 return,默认返回 None
  • 返回多个值(逗号分隔)实际上是在构造一个元组(Tuple)。
  • 在文档中明确函数的副作用:它是修改了传入的参数(In-place mutation),还是返回了新对象?是否涉及 I/O 操作?是否持有对象引用?这些细节对调用方至关重要。

注解与文档字符串

  • 类型注解(Annotations):主要服务于静态分析工具(如 Mypy)和开发者阅读,Python 解释器在运行时通常不强制校验类型。
  • 文档字符串(Docstrings):不要重复签名。重点描述函数的行为、关键不变量(Invariants)、可能抛出的异常以及非显而易见的副作用。

设计准则

  • 分离关注点:尽可能将纯计算逻辑与 I/O 操作分离,便于测试和复用。
  • 显式返回:在可复用逻辑中,优先返回明确的结果值,避免在函数内部直接 print
  • 异常边界:在识别出无效状态的边界抛出具体异常,而非在深层逻辑中吞掉或模糊处理。
  • 抽象层级:保持函数在单一抽象层级上,避免一个函数既处理高层业务逻辑又处理底层细节。

局部作用域与共享实参

按 Python 的名称解析规则,普通函数代码依次查找局部名称、外层函数作用域、模块和内置名称。函数中只要出现对某名称的赋值,该名称通常就在整个函数内被视为局部名称;在赋值前读取它会抛出 UnboundLocalError,即使模块中有同名变量也一样。global 指向模块绑定,nonlocal 指向已存在的外层函数绑定。单纯读取名称或修改所引用的列表不需要这两种声明。

def modify(items):
items.append(2) # 修改调用方传入的对象
items = [99] # 只重新绑定局部名称
return items

numbers = [1]
assert modify(numbers) == [99]
assert numbers == [1, 2]
assert collect("a") == ["a"]
assert collect("b") == ["b"]
bucket = []
assert collect("c", bucket) is bucket
assert clamp(12, 0, 10) == 10
assert clamp(5, 0, 10, strict=True) == 5

clamp 示例假定输入是可排序的有限数值,不负责验证 NaN 或任意对象。clamp(5, 0, 10, True) 会抛出 TypeError,因为 strict 只能按关键字传入。缺少必填实参、重复绑定同一形参、传入不接受的关键字也会抛出 TypeError。调用中的 *values 展开位置实参,**options 将映射展开为关键字实参。def 绑定函数对象时不会执行函数体;函数也可以作为值传入其他函数或由其返回。

参考来源

探索关联打开关联网络