17370845950

Python 怎么让一个函数既能当普通函数又能当装饰器用?
判断函数被当装饰器还是普通调用,关键看func参数是否为None且是否为函数:func为None则走装饰器路径并返回闭包;func为函数则直接包装执行。

函数怎么判断自己被当装饰器还是普通函数调用

关键在调用时的参数类型:如果第一个参数是函数(callable(arg)not isinstance(arg, (str, type))),大概率是被当装饰器用了;否则就是普通调用。但要注意,不能只

len(args) == 1 判断——用户可能传一个函数加一堆 keyword 参数,比如 @mydec(verbose=True)

带参装饰器和无参装饰器的嵌套结构差异

真正能“两用”的函数,本质是模拟了 Python 装饰器协议的两种形态:
• 普通调用:直接执行逻辑
• 装饰器调用:返回一个闭包,该闭包接收被装饰函数

典型结构是两层嵌套:

def mydec(func=None, *, verbose=False):
    if func is not None:
        # 无参装饰器写法:@mydec
        return _wrap(func, verbose=verbose)
    # 带参装饰器写法:@mydec(verbose=True) → 先调用 mydec(verbose=True),再等它返回装饰器
    return lambda f: _wrap(f, verbose=verbose)

def _wrap(func, verbose): def wrapper(*args, *kwargs): if verbose: print(f"Calling {func.name}") return func(args, **kwargs) return wrapper

注意:func=None*(强制 keyword-only 参数)是必须的,否则无法区分 @mydecmydec()

容易踩的坑:可调用对象误判和 functools.wraps 缺失

以下情况会导致误判:

  • 把类实例(实现了 __call__)当普通函数传进来,callable(obj) 返回 True,但它不是被装饰的目标函数
  • 用户传了个字符串或数字,恰好名字叫 "func",但你用 isfunction() 检查失败,又没 fallback 处理,就崩了
  • 没用 functools.wraps(func) 包裹内部 wrapper,导致被装饰函数的 __name____doc__ 全丢,调试时找不到源头

稳妥做法是:优先检查 func 是否为 None,再检查是否是函数(inspect.isfunction(func)isinstance(func, types.FunctionType)),其他情况统一走装饰器路径。

实际使用时要不要支持两用

大多数场景下,不建议强行两用。原因很实在:

  • 用户心智负担增加:ta 得记住 @decdec() 都合法,但行为不同
  • IDE 和类型检查器(如 mypy)很难推断这种重载,标注 Overload 也容易出错
  • 一旦加了新参数(比如 cache=True),两用逻辑会迅速变得脆弱

更清晰的做法是分两个函数:my_util() 做普通工具,my_decorator() 专做装饰器——名字明确,类型安全,debug 时一眼看懂。