如何让函数参数默认值是工厂函数(每次调用新建)

应使用None作默认参数并在函数内调用工厂函数生成新对象,避免可变默认参数陷阱;因默认参数在定义时求值一次,直接写func(x=[])会导致多次调用共享同一对象。

为什么不能直接写 func(x = [])

Python 中默认参数在函数定义时就求值一次,不是每次调用时新建。所以 def f(x = [])[] 是同一个列表对象,多次调用会累积修改——这是经典陷阱。工厂函数本身不解决这个问题,除非你让它“延迟执行”。

正确做法:用 None 作占位符 + 函数内调用工厂

把默认值设为 None,在函数体里判断并调用工厂函数生成新对象。这是最清晰、最易读、兼容所有 Python 版本的方式。

示例:

def create_item(items=None):
    if items is None:
        items = list()  # 或 lambda: [], 或其他工厂逻辑
    items.append("new")
    return items
  • 工厂逻辑可以是 list()dict()MyClass(),也可以是自定义函数如 lambda: [0] * 3
  • 避免用 is []== [] 判断,默认值是 None 才可靠
  • 如果工厂

    开销大(比如读文件、建连接),这个模式也天然支持按需创建

想更“声明式”?封装一个装饰器或基类

如果你大量函数都需要这种行为,可以抽象一层,但别过早优化。简单场景下,硬编码 if x is None: x = factory() 更直白、调试更友好。

装饰器示例(仅作参考,非推荐首选):

def lazy_default(**factories):
    def decorator(func):
        def wrapper(**kwargs):
            for k, factory in factories.items():
                if k not in kwargs or kwargs[k] is None:
                    kwargs[k] = factory()
            return func(**kwargs)
        return wrapper
    return decorator

@lazy_default(items=lambda: []) def process(items): items.append("done") return items

  • 这个装饰器只处理关键字参数,且要求调用时显式传 items=None 才触发工厂
  • 它掩盖了参数的实际初始化时机,对 IDE 类型推导、静态检查都不友好
  • 一旦工厂函数抛异常,堆栈会变深,debug 成本上升

注意:不要用 functools.partial 模拟工厂默认值

有人试图写 def f(x=partial(list)),这是错的——partial 对象本身不是可调用的“结果”,而是一个待绑定的对象;你得手动 x() 才能拿到新列表,这破坏了函数签名语义。

  • partial(list) 不等于 list(),前者是构造器,后者是调用结果
  • 类型提示(如 x: list = partial(list))会失效,mypy 会报错
  • 调用方无法直观理解这个参数“应该传什么”,可维护性差

真正需要每次新建,默认值必须延迟到函数体内执行。最安全的路径就是守住 None 占位 + 显式工厂调用这条线。其它包装方案在多数项目里只是增加认知负担。