判断函数被当装饰器还是普通调用,关键看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 参数)是必须的,否则无法区分 @mydec 和 mydec()。
以下情况会导致误判:
__call__)当普通函数传进来,callable(obj) 返回 True,但它不是被装饰的目标函数"func",但你用 isfunction() 检查失败,又没 fallback 处理,就崩了functools.wraps(func) 包裹内部 wrapper,导致被装饰函数的 __name__、__doc__ 全丢,调试时找不到源头稳妥做法是:优先检查 func 是否为 None,再检查是否是函数(inspect.isfunction(func) 或 isinstance(func, types.FunctionType)),其他情况统一走装饰器路径。
大多数场景下,不建议强行两用。原因很实在:
@dec 和 dec() 都合法,但行为不同Overload 也容易出错cache=True),两用逻辑会迅速变得脆弱更清晰的做法是分两个函数:my_util() 做普通工具,my_decorator() 专做装饰器——名字明确,类型安全,debug 时一眼看懂。