前言:
写代码嘛,关键是得让它既好用又好看,这不,Python装饰器就摆在那儿。咱们程序员有时也得有那么点艺术家的腔调:讲求效率,追求代码的简洁优雅,偶尔还得装装X,不是吗?
翻开人家的工程代码,哎呦,有时候我都忍不住想吐槽,怎么就这么啰嗦?这不,咱就得拿出刀子,削削刻刻,把装饰器这玩意儿的妙用给咱整明白了。得让代码那叫一个流畅,看着清清楚楚,懂得一看就会,会了还想看。
正文:
1. @staticmethod
作用
@staticmethod 装饰器允许我们定义一个方法,使其不依赖于对象的状态(即不与特定的实例self绑定,也不与类cls绑定)。这主要用在你想将某个功能置于类的命名空间(namespace)下,以表明它逻辑上属于类,但实际上又独立于类的任何特定实例。
用法
当你需要直接通过类调用方法而不需要实例时,将该方法定义为静态方法是非常合适的。
class MathOperations:@staticmethoddef add(x, y):"""返回两个参数的和。"""return x + y# 调用静态方法,注意如何使用类名直接调用静态方法,而不是类的实例
result = MathOperations.add(5, 10)
print(result) # 输出: 15
代码讲解
在上述代码中,add 方法被 @staticmethod 装饰,意味着它不接受常规方法第一个参数(self)。这使得它可以像普通函数一样通过类名直接调用,而不需要创建类的实例。
对比
不使用 @staticmethod 的普通方法会要求传递一个实例作为第一个参数。下面是未使用 @staticmethod 的对比:
class MathOperations:def add(self, x, y):"""返回两个参数的和。需要通过实例调用。"""return x + y# 创建类的实例
math_ops = MathOperations()
result = math_ops.add(5, 10)
print(result) # 输出: 15
在这个例子中,我们必须先创建一个 MathOperations 类的实例,并通过这个实例调用 add 方法。
2.@classmethod
作用
@classmethod 装饰器使方法变成类方法,它接收类本身作为第一个参数,通常命名为 cls。这使得这个方法可以访问类属性或调用其他类方法,而不依赖于具体的实例。
用法
类方法通常用作替代构造函数。
class MyClass:_my_list = []@classmethoddef add_to_list(cls, item):"""将项目添加到类属性列表中。"""cls._my_list.append(item)MyClass.add_to_list('item 1')
print(MyClass._my_list) # 输出: ['item 1']
代码讲解
在上述代码中,add_to_list 方法是一个类方法,可以直接通过类来调用,并且它修改了类属性 _my_list。
对比
常规的实例方法需要创建实例才能调用,并且只能访问实例属性:
class MyClass:def __init__(self):self._my_list = []def add_to_list(self, item):"""将项目添加到实例属性列表中。"""self._my_list.append(item)# 创建类的实例
my_instance = MyClass()
my_instance.add_to_list('item 1')
print(my_instance._my_list) # 输出: ['item 1']# 尝试使用类名直接调用将会失败
# MyClass.add_to_list('item 1') # 这将抛出 TypeError
3. @property
作用
@property 装饰器用于将类中的方法当作一个属性来访问,通常用于计算或返回内部状态的值,同时不让调用者直接访问内部状态。
用法
它经常被用于创建只读属性。
class MyClass:def __init__(self, value):self._internal_state = value@propertydef internal_state(self):"""访问内部状态的一个只读属性"""return self._internal_state# 创建类实例
instance = MyClass(5)
print(instance.internal_state) # 输出: 5
代码讲解
在上面的代码中,internal_state 成为一个只读属性,我们不能直接对它赋值(除非也定义了 setter)。
对比
没有 @property 装饰器,我们通常会直接公开属性或通过一个明确的 getter 方法来访问:
class MyClass:def __init__(self, value):self.internal_state = value# 创建类实例
instance = MyClass(5)
print(instance.internal_state) # 输出: 5
在这个例子中,internal_state 可以被外部直接访问和修改,没有了 @property 提供的保护层。
4. @property.setter
作用
@property.setter 装饰器是与之前的 @property 配套使用的,它允许我们定义一个赋值方法,可以通过这个属性对相关数据进行设置。
用法
我们可以用它来定义一个属性的设置逻辑,例如实施类型检查或值验证。
class MyClass:def __init__(self, value):self._internal_state = value@propertydef internal_state(self):return self._internal_state@internal_state.setterdef internal_state(self, value):if not isinstance(value, int):raise ValueError("internal_state must be an integer")self._internal_state = value# 创建类实例
instance = MyClass(5)
instance.internal_state = 10 # 有效
print(instance.internal_state) # 输出: 10
代码讲解
在这个例子中,尝试为 internal_state 赋非整数值会引发 ValueError,因为我们的 setter 方法中添加了类型检查。
对比
如果没有 @property.setter,我们不能定义一个属性的赋值行为,属性将是完全公开的:
class MyClass:def __init__(self, value):self.internal_state = value# 创建类实例
instance = MyClass(5)
instance.internal_state = 'a string' # 没有问题,但可能不是我们想要的行为
在没有 setter 装饰器的情况下,internal_state 属性可以接受任何类型的赋值,没有额外的验证逻辑来保护类的内部表示。
5. @functools.wraps
作用
装饰器在Python中用于增加函数额外的功能,但装饰器会改变函数的__name__和__doc__属性。@functools.wraps是一个特殊的装饰器,用于在定义装饰器时保持原函数的元数据不变。
用法
这通常用于自定义装饰器的编写中,以确保被装饰函数的元数据不会丢失。
import functoolsdef my_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("函数发生前...")result = func(*args, **kwargs)print("函数发生后")return resultreturn wrapper@my_decorator
def say_hello():"""Greet the user."""print("Hello!")# 由于使用了 functools.wraps,say_hello 保留了其原始的元数据
print(say_hello.__name__)
print(say_hello.__doc__)
代码讲解
在这个例子中,functools.wraps用在内部函数wrapper上,保护了原始函数say_hello的__name__和__doc__属性。如果不使用functools.wraps,say_hello.__name__将会返回'wrapper'。
对比
没有@functools.wraps,函数say_hello的属性会被丢失:
def my_decorator(func):def wrapper(*args, **kwargs):"""Wrapper function for func."""print("函数get前发生的...")result = func(*args, **kwargs)print("函数get后发生的...")return resultreturn wrapper@my_decorator
def say_hello():print("Hello!")# 没有使用 functools.wraps,元数据被覆盖了
print(say_hello.__name__)
print(say_hello.__doc__)
在没有使用functools.wraps的这个版本中,装饰函数wrapper的__name__和__doc__覆盖了say_hello的元数据,这会在某些情况下导致混淆,特别是在需要函数签名保持原样时。
6. @functools.lru_cache
作用
@functools.lru_cache 提供了一个简单的缓存机制,让函数能缓存最近使用过的输入及其结果。如果一个函数被频繁调用,并且伴随相同的参数,LRU(Least Recently Used)缓存可以提升性能。
用法
它特别用于那些计算开销大但又有高重复调用概率的函数。
import functools@functools.lru_cache(maxsize=32)
def fibonacci(num):"""返回斐波那契数列的第 num 个数"""if num < 2:return numreturn fibonacci(num - 1) + fibonacci(num - 2)# 第一次调用将会缓存结果
print(fibonacci(10)) # 输出: 55
# 后续调用将会使用缓存
print(fibonacci(10)) # 输出: 55
代码讲解
在这个例子中,fibonacci 函数的每个结果会在首次计算后缓存起来。当你再次用同样的参数调用该函数时,它会返回缓存的结果而不是重新计算。
对比
不使用 @functools.lru_cache 运行上面的 fibonacci 函数会导致很多重复的计算,特别是参数较大时会非常耗时。
7. @functools.singledispatch
作用
@functools.singledispatch 装饰器可以将普通函数转化为单分派泛函数。这表示函数将会根据第一个参数的类型,调用另外专门定义的函数,实现类似于其他语言中的函数重载。
用法
它特别适用于需要根据不同类型执行不同操作的场景。
import functools@functools.singledispatch
def format_data(arg):"""默认的实现,被调用时参数类型没有匹配的特定注册函数"""return str(arg)@format_data.register
def _(arg: int):"""专门用于处理整数的实现"""return f"{arg} is an integer"@format_data.register
def _(arg: list):"""专门用于处理列表的实现"""return f"List length is {len(arg)} and items are {', '.join(map(str, arg))}"# 根据参数类型调用不同的实现
print(format_data("Hello!"))
print(format_data(42))
print(format_data([1, 2, 3]))
代码讲解
在这个例子中,对于任意未注册类型,format_data 将使用默认实现,即直接转换为字符串。对于被注册类型,例如 int 和 list,将调用专为它们定义的函数。
对比
若没有 @functools.singledispatch,你通常需要编写显式的类型检查语句(如使用 if-elif-else 结构),这样的代码通常会更加冗长和不易维护。
总结:
好了,我的粉儿们,咱们今天的`装饰器课`就上到这儿。
记住,装饰器不光让你的代码行数变短,颜值变高,更重要的是,它能让你的工作流、逻辑变得更清晰。
- @staticmethod和@classmethod能帮你搞定静态和类相关的调用;
- @property 让访问方法像是读取属性一样优雅;
- @functools.wraps,让你自定义装饰器的时候还能保持原函数的尊严和名声。
- 然后是咱们的性能加速器@functools.lru_cache,让你对那些费劲儿的计算结果记个小本本,下次用的时候直接从记忆里找,不再重新煮沸冷饭,节约不少脑力。
- 至于@functools.singledispatch,简直就是动态语言Python的类型挂钩利器,让同一个名字的函数根据传参的不同表演不同的戏法。
啰嗦了这么多,希望你们明白:代码写得好不好,跟会不会用装饰器,那可是直接挂钩的。用好了,你的代码就像是精品咖啡,不用或者用糟了,那就是旅馆速溶。行了,回家练去吧。
保持好奇,保持饥渴,这样才能写出更香的代码。加油!