1. 闭包基础
知识点:
- 闭包:由内外函数构成。外函数返回内函数,而内函数引用了外函数的变量,从而实现“闭包”效果,保证局部变量在函数调用结束后依然被保存。
代码示例:
def outer():x = 500 # 外函数变量def inner():y = 200return x + y # 内函数引用了外部变量 xreturn inner # 返回内函数本身,而非调用# 使用闭包
func = outer()
print("闭包返回结果:", func())
函数实现装饰器
2. 装饰器基础
知识点:
- 装饰器:本质上是一种闭包,用于在不修改原函数代码的前提下,动态地为函数添加额外功能。下面示例中定义了一个
costTime
装饰器,用于统计函数执行时间。
代码示例:
import timedef costTime(func):print("执行了costTime函数") # 装饰器在装饰时会执行一次def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs) # 调用原函数end = time.time()print(f"执行{func.__name__}花了{end - start}s")return resultreturn inner@costTime # 等价于 add = costTime(add)
def add(a, b):time.sleep(1) # 模拟耗时操作return a + bprint("add(1, 2) =", add(1, 2))
注意: 使用装饰器后,原函数 add
实际上变成了 inner
函数,元数据(如函数名)会发生变化。
3. 保留原函数元数据
知识点:
- 为了避免装饰器覆盖原函数的元数据(例如
__name__
、__doc__
),可以使用functools.wraps
装饰内层函数,使得原函数的元数据得以保留。
代码示例:
import time
from functools import wrapsdef costTime(func):print("执行了costTime函数")@wraps(func)def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"执行{func.__name__}花了{end - start}s")return resultreturn inner@costTime
def add(a, b):"""求两个数的和"""time.sleep(1)return a + bprint("add(1, 2) =", add(1, 2))
print("函数名称:", add.__name__)
print("函数文档:", add.__doc__)
4. 带参数的装饰器
知识点:
- 带参数的装饰器:需要使用三层函数来实现。最外层函数接收装饰器参数,中间层函数接收原函数,最内层函数完成对原函数的调用以及附加功能。
代码示例:
import timedef deco(name="root"):# 最外层函数,接收装饰器参数def costTime(func):# 中间层,接收原函数def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"执行{func.__name__}花了{end - start}s")print(f"传递的参数为:{name}")return resultreturn innerreturn costTime@deco(name="admin")
def add(a, b):time.sleep(1)return a + bprint("add(1, 2) =", add(1, 2))
1. 闭包与装饰器基本原理
-
闭包概念
- 闭包由外部函数和内部函数构成。内部函数可以访问外部函数的局部变量,即使外部函数已执行完毕。
-
装饰器本质
- 装饰器利用闭包特性,在不修改原函数代码的情况下,为其添加额外功能,如日志记录、性能计时、权限验证等。
- 装饰器在定义时会先执行外层逻辑(例如打印提示),而返回的内部包装函数在调用时添加附加操作,再调用原函数。
2. 装饰器基础写法
- 装饰器函数通常接收一个函数作为参数,并返回一个新的包装函数。
- 包装函数内通常先执行预处理逻辑(如记录开始时间),再调用原函数,最后执行后续操作(如计算耗时、打印结果)。
- 这种模式实现了对原函数功能的无侵入式扩展。
3. 保留原函数元数据
- 使用装饰器后,包装函数会覆盖原函数的名称、文档字符串等元数据。
- 为了保留这些信息,可以使用
functools.wraps
装饰包装函数,使得包装函数复制原函数的元数据。
4. 带参数的装饰器
- 带参数的装饰器需要通过三层函数嵌套实现:
- 最外层函数:接收装饰器参数,用于动态配置装饰行为。
- 中间层函数:接收原函数,并返回包装函数。
- 最内层函数:在调用原函数前后添加额外逻辑,同时传递所有参数。
- 这种结构使装饰器可以灵活地根据传入参数调整附加功能。
5. 总结
- 装饰器是 Python 中一种强大的功能扩展机制,基于高阶函数和闭包实现。
- 常见应用场景包括性能统计、日志记录、缓存、权限控制等。
- 设计装饰器时,注意保留原函数元数据(使用
functools.wraps
)和正确处理参数传递问题。 - 带参数的装饰器结构虽然稍复杂,但能够大幅提升装饰器的灵活性和可配置性。
以下是关于使用类方法实现装饰器以及魔术方法__call__
的学习笔记:
类实现装饰器
1. 使用类实现装饰器
在Python中,装饰器通常用于在不修改原函数代码的情况下,给函数增加额外的功能。除了使用函数实现装饰器外,还可以通过类来实现。这需要在类中实现__call__
方法,使其实例化对象变为可调用对象,类似于函数。
示例:
import timeclass DecoClass:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):start = time.time()result = self.func(*args, **kwargs)end = time.time()print(f"执行{self.func.__name__}花了{end - start}s")return result@DecoClass # 等同于:add = DecoClass(add)
def add(a, b):time.sleep(1)return a + bprint(add(1, 2))
说明:
DecoClass
类的__init__
方法接收一个函数作为参数,并将其赋值给实例变量self.func
。__call__
方法使得类的实例对象可以像函数一样被调用。在调用时,记录函数执行前后的时间,并计算执行时间。- 使用
@DecoClass
语法糖,将add
函数装饰,使其在调用时自动计算并输出执行时间。
2. 带参数的装饰器类实现
有时,我们希望装饰器本身也能接受参数。这可以通过在类的__init__
方法中接收装饰器参数,并在__call__
方法中再定义并返回一个内部函数来实现。
示例:
import timeclass DecoClass:def __init__(self, name):self.name = namedef __call__(self, func):def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"执行{func.__name__}花了{end - start}s")return resultreturn inner@DecoClass(name="admin") # 等同于:add = DecoClass(name="admin")(add)
def add(a, b):time.sleep(1)return a + bprint(add(1, 2))
说明:
DecoClass
类的__init__
方法接收装饰器参数name
,并将其赋值给实例变量self.name
。__call__
方法接收一个函数func
作为参数,并定义一个内部函数inner
,用于包装原函数的执行,并计算其执行时间。- 使用
@DecoClass(name="admin")
语法糖,将add
函数装饰,使其在调用时自动计算并输出执行时间。
3. 魔术方法__call__
__call__
是Python中的一种魔术方法,使得类的实例对象可以像函数一样被调用。通过在类中实现__call__
方法,可以定义实例对象被调用时的行为。这在实现装饰器、回调函数等场景中非常有用。
示例:
class MyCallable:def __init__(self, value):self.value = valuedef __call__(self, increment):self.value += incrementprint(f"Value is now {self.value}")obj = MyCallable(10)
obj(5) # 输出:Value is now 15
说明:
MyCallable
类实现了__call__
方法,因此其实例对象obj
可以像函数一样被调用。- 调用
obj(5)
时,实际执行的是obj.__call__(5)
,这使得self.value
增加了increment
的值。