一、最基础: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()执行流程
def my_decorator:定义装饰器函数(不跑)。def say_hello:定义原函数(不跑)。@my_decorator等价于say_hello = my_decorator(say_hello):- 调用 my_decorator,传入原函数;
- 内部定义 wrapper(不跑),
return wrapper把 wrapper 交出去; say_hello这个名字现在指向 wrapper(不再是原函数)。
say_hello()实际执行wrapper():
函数执行前 Hello! 函数执行后关键结论
@装饰器本质 =函数名 = 装饰器(函数名),把名字重新绑定到新函数。- 装饰器执行时(
@那行)只定义和返回,不打印;打印发生在你后续调用时。 - 没有调用那行 → 什么都不输出。
三、Python 的"指向"不是 C 指针(重要纠偏)
❗ 当时类比错了的地方,已全部纠正:
原错误类比 | 正确概念 |
方法是内存块,指针从上往下扫到哪执行到哪 |
|
指向像 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':'小澪'}。
写法 | 接住 | 存为 |
| 位置参数(按顺序的值) | 元组 tuple |
| 关键字参数(名字=值) | 字典 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 |
| 配置参数 3 |
|
2 |
| 被装饰函数 say_hello |
|
3 |
| 调用时参数 |
|
❗ 当时误解:@repeat(3)把 say_hello 传给了 repeat。错!
repeat(3)先算 → 传num_times=3→ 返回decorator(内部记住 num_times=3)。- 然后
@decorator才把say_hello传给decorator的func。 - 不是同一个函数接的两个东西,是先吃配置、再吐出一个定制装饰器去接函数。
对比无参装饰器
无参 | 带参 |
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 个数"。
七、今日核心心法(背下来)
def造函数不跑,名字()才跑。
@装饰器=函数名 = 装饰器(函数名),名字被重新指向新函数。
- 没写调用 → 不输出;要输出必须显式
名字()。
- 名字是引用不是指针,内存 Python 自动管。
(*args, **kwargs)是装饰器万能接口,定义处收集、调用处解包。
- 带参装饰器多一层"工厂":先收配置、再产装饰器、最后接函数。
range(n)+for= 循环 n 次;_是占位变量。