岗位未披露的 Python 技术电面 · 一小时技术电面,Python 基础

Nvidia Python 基础题:Decorator 如何工作,有什么好处?

解释 Python 装饰器的函数重绑定、闭包、参数转发、wraps 元数据、叠加顺序和实际用途,并指出包装器改变调用语义的风险。

题目重点:解释 Python 装饰器的函数重绑定、闭包、参数转发、wraps 元数据、叠加顺序和实际用途,并指出包装器改变调用语义的风险。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 以同步函数的调用计时装饰器作训练例;不把计时实现视为题目题面。
  • 被装饰函数参数与返回值均原样转发;异步函数需改用可 await 的包装器。

解题路线

  1. 先说语法:`@dec` 放在函数定义前,效果等同定义函数后执行 `f = dec(f)`;decorator 接受 callable 并返回新 callable,也可以先由带参数工厂产生 decorator。
  2. 包装器闭包持有原函数,用 `*args, **kwargs` 转发,返回原结果;若计时或记录日志,`try/finally` 可确保异常时也记录结束,但异常应继续向外传递。
  3. 用 `functools.wraps(f)` 保留名称、文档、注解及 `__wrapped__` 入口,方便 introspection 和工具。多个装饰器如 `@A` 在 `@B` 上方,相当于 `f = A(B(f))`:B 先装饰,调用时 A 外层先运行。
  4. 说明好处是把鉴权、缓存、重试、日志等横切逻辑复用到多个函数;同时提醒缓存 key、权限上下文、异常、异步语义和装饰顺序必须设计清楚。

手动走一遍

训练例 `@log` 修饰 `load(id)` 后,`load(7)` 先进入 wrapper,再调用原始 load 并返回结果。若 `@auth` 在 `@cache` 之上,调用时先 auth 再 cache;调换次序可能让缓存命中绕开授权检查,因此顺序是行为的一部分。

规模与取舍

普通包装器每次调用通常增加 O(1) 分发开销,原函数复杂度不变;日志序列化、缓存查找或重试会各自引入额外时间与空间。叠加 D 层装饰器有 O(D) 的包装调用深度。

易错点与追问

容易失分的边界

  • 认为 `@` 是特殊执行器,而不解释函数重新绑定。
  • 忘记返回原函数结果、吞掉异常或丢掉参数/关键字参数。
  • 不使用 wraps 导致函数签名与文档工具失真。
  • 把同步 wrapper 用于 async 函数却不 await,或把鉴权和缓存顺序写反。

继续深挖

  • 带参数装饰器为何有工厂、decorator、wrapper 三层?
  • 如何支持 async def 并保留异常与取消语义?
  • 叠加缓存与权限装饰器时怎样避免跨用户结果泄漏?

技术参考

✓已复制微信号!