Python 装饰器与函数调用机制(复习笔记 · 2026-07-07)

📅 2026/7/8 3:48:34 👁️ 阅读次数 📝 编程学习
Python 装饰器与函数调用机制(复习笔记 · 2026-07-07)

一、最基础:def 是定义,() 是调用

  • def say_hello(): print("Hello!")——只定义,不执行。函数体被"存起来",不打印任何东西。
  • say_hello()——有括号 = 调用 = 真正执行,才会打印 "Hello!"。
  • 如果删掉调用那行,程序什么都不输出(函数只定义、从没被喊去跑)。

❗ 误区:以为@装饰器会自动让函数运行。不会,必须显式写名字()才执行。

二、无参装饰器:名字被重新贴到 wrapper 上

def my_decorator(func): def wrapper(): print("函数执行前") func() print("函数执行后") return wrapper @my_decorator def say_hello(): print("Hello!") say_hello()

执行流程

  1. def my_decorator:定义装饰器函数(不跑)。
  2. def say_hello:定义原函数(不跑)。
  3. @my_decorator等价于say_hello = my_decorator(say_hello)
    • 调用 my_decorator,传入原函数;
    • 内部定义 wrapper(不跑),return wrapper把 wrapper 交出去;
    • say_hello这个名字现在指向 wrapper(不再是原函数)。
  4. say_hello()实际执行wrapper()
函数执行前 Hello! 函数执行后

关键结论

  • @装饰器本质 =函数名 = 装饰器(函数名),把名字重新绑定到新函数。
  • 装饰器执行时(@那行)只定义和返回,不打印;打印发生在你后续调用时。
  • 没有调用那行 → 什么都不输出。

三、Python 的"指向"不是 C 指针(重要纠偏)

❗ 当时类比错了的地方,已全部纠正:

原错误类比

正确概念

方法是内存块,指针从上往下扫到哪执行到哪

def语句本身执行了(创建函数对象),但函数体不执行,只是存起来等调用

指向像 C 指针,知道内存哪块就跳去执行

Python 名字是引用/reference(标签贴对象),不是 C 指针,不能指针运算

占多少内存要关心

函数对象很小(几百字节),Python 有自动 GC,学习阶段不用管内存

Python 有指针

没有指针;有调用栈、有 list(动态数组)、有 numpy.ndarray(AI 必用)

Java 学习者对照表(学过 Java,基本能直接迁移)

Java 认知

Python 对应

方法定义不运行,要 obj.method() 才调

✅ 一样,def 不跑,func() 才跑

对象靠引用、有 GC、有调用栈/堆

✅ Python 一样,且一切皆对象(连函数都是)

primitive 在栈、对象在堆

Python 更统一:全是对象、全靠引用

无裸指针、不能指针运算

✅ Python 也没有

要 new 才创建对象

Python不用 new,赋值即创建

要手动 free/delete

Python 自动 GC,不用管

唯一要"忘掉"的:C 的手动内存管理 + 指针算术。Python 比 Java 还省心。

四、(*args, **kwargs):装饰器的"万能接口"

def wrapper(*args, **kwargs): result = original_function(*args, **kwargs) return result
  • 调用target_function()(空括号)→args=()空元组、kwargs={}空字典,啥都没接
  • target_function(1, 2, name="小澪")args=(1,2)kwargs={'name':'小澪'}

写法

接住

存为

*args

位置参数(按顺序的值)

元组 tuple

**kwargs

关键字参数(名字=值)

字典 dict

  • 定义处的*/**=收集(打包收到的参数)。
  • 调用处的original_function(*args, **kwargs)*/**=解包(摊开传回去)。
  • 作用:让装饰器通用,不管被装饰函数要不要参数都能透传。无参函数传空进去正好。

❗ 误区:以为(*args, **kwargs)是"必须填两个"。不是,是"能接住任意参数"的兜底,不传就都是空的。

五、带参装饰器(装饰器工厂)= 多一层

def repeat(num_times): def decorator(func): def wrapper(*args, **kwargs): for _ in range(num_times): func(*args, **kwargs) return wrapper return decorator @repeat(3) def say_hello(): print("Hello!") say_hello()

三层接参(最关键,别混)

函数

接住

来自

1

repeat(num_times)

配置参数 3

repeat(3)

2

decorator(func)

被装饰函数 say_hello

@语法

3

wrapper(*args, **kwargs)

调用时参数

say_hello()

❗ 当时误解:@repeat(3)把 say_hello 传给了 repeat。错!

  • repeat(3)先算 → 传num_times=3→ 返回decorator(内部记住 num_times=3)。
  • 然后@decorator才把say_hello传给decoratorfunc
  • 不是同一个函数接的两个东西,是先吃配置、再吐出一个定制装饰器去接函数。

对比无参装饰器

无参@df

带参@repeat(3)

df 直接接原函数

repeat 先接 3 → 吐 decorator;decorator 再接原函数

两层(df / wrapper)

三层(repeat / decorator / wrapper)

多出的repeat层唯一作用:接收配置参数,返回真正能装饰函数的 decorator→ 所以叫"装饰器工厂"。

六、for _ in range(n):循环 n 次

  • range(3)产生0, 1, 2(从 0 到 n-1,共 n 个)。
  • for _ in range(n):= "循环 n 次",每轮执行缩进块。
  • _是占位变量名,表示"这个数我不用",纯占位(写for i in ...也一样,只是 i 没被用)。
  • 带参装饰器打印三次,就是for _ in range(num_times)num_times=3决定的;改成 5 就打印 5 次。
  • range()不是列表,是惰性序列,省内存,学习阶段只需知道"range(n)= 0 到 n-1 共 n 个数"。

七、今日核心心法(背下来)

  1. def造函数不跑,名字()才跑。
  1. @装饰器=函数名 = 装饰器(函数名),名字被重新指向新函数。
  1. 没写调用 → 不输出;要输出必须显式名字()
  1. 名字是引用不是指针,内存 Python 自动管。
  1. (*args, **kwargs)是装饰器万能接口,定义处收集、调用处解包。
  1. 带参装饰器多一层"工厂":先收配置、再产装饰器、最后接函数。
  1. range(n)+for= 循环 n 次;_是占位变量。