目录
一、装饰器的定义:
二、装饰器类型:
三、装饰器的主要用途:
四、classmethod常用场景:
1、classmethod函数:
1-1、Python:
1-2、VBA:
2、相关文章:
classmethod是 Python 中的一个装饰器,用于表示一个方法是类级别的方法,而不是实例级别的方法。使用 `@classmethod` 装饰器的方法接收的第一个参数是类本身(通常命名为 `cls`,只要不与Python中相关的内置信息相冲突即可,一般没有硬性规定,但要注意与实例对象的区分开,以免混淆,难于理解,且不利于后期代码维护),而不是实例对象(通常命名为 `self `)。
一、装饰器的定义:
装饰器(Decorator)是一种在不修改原始函数或类代码的情况下,通过包装函数或类来扩展其功能的技术。它基于Python的函数式编程思想,可以更好地实现代码的重用性和可读性,避免了代码重复而造成的冗长和混乱。
装饰器通常是一个函数,它接受一个函数或类作为参数,并返回一个新的函数或类。这个新函数或类通常会在调用原始函数或类之前或之后执行一些额外的逻辑。装饰器可以用于添加日志记录、性能分析、输入验证、缓存处理等功能。
在Python中,装饰器使用`@`符号紧跟在函数或类定义的上方,表示将函数或类装饰为另一个函数或类。装饰器函数可以接受任意数量的参数,并返回一个新的函数或类。
装饰器按照功能可以分为函数装饰器和类装饰器。函数装饰器应用于函数,而类装饰器应用于类。使用类装饰器还可以依靠类内部的`__call__`方法,当使用`@`形式将装饰器附加到函数或类上时,就会调用此方法。
总的来说,装饰器(Decorator)是解决特定编程问题的一种有效设计,可以抽离出大量与函数或类功能本身无关的雷同代码,提高代码的可读性和可维护性。
二、装饰器类型:
Python中的装饰器种类非常丰富,涵盖了各种不同的功能和应用场景。以下是一些常见的Python装饰器类型:
1、函数装饰器:
1-1、@staticmethod:将方法转换为静态方法,使其不依赖于类实例,而是直接在类上调用。
1-2、@classmethod:将方法转换为类方法,该方法接收类本身作为第一个参数,而不是实例对象。
1-3、@property:将一个方法转换为属性,允许像访问数据属性一样调用方法。
1-4、@functools.lru_cache:实现最近最少使用(LRU)缓存,用于缓存函数结果。
1-5、@wraps:保留被装饰函数的元数据,如函数名、文档字符串等。
2、类装饰器:
2-1、类装饰器通常用于修改类定义或创建新的类。
2-2、它们可以动态地添加、修改或删除类属性或方法。
3、通用功能装饰器:
3-1、@timer:测量函数的执行时间,用于性能分析。
3-2、@memoize:缓存函数结果,避免重复计算。
3-3、@logger:记录函数或类的调用日志。
3-4、@repeat:重复执行函数指定的次数。
3-5、@singleton:确保类只有一个实例。
3-6、@contextmanager:将生成器函数转换为上下文管理器,用于简化资源管理(如文件打开和关闭)。
4、异步装饰器:
4-1、@asyncio.coroutine:在Python 3.7之前的版本中,用于标识异步函数。
4-2、@sync_to_async/@async_to_sync:在Django的异步支持中,用于将同步函数转换为异步函数,或将异步函数转换为同步函数。
5、权限和安全性装饰器:
5-1、这类装饰器通常用于验证用户权限、检查API密钥等,以确保只有授权用户才能访问特定的函数或类。
6、API路由装饰器:
6-1、在web框架(如Flask、FastAPI)中,装饰器常用于定义路由和处理HTTP请求。
7、数据验证和序列化装饰器:
7-1、用于在函数或方法调用前后验证输入数据或序列化输出数据。
8、自定义装饰器:
8-1、根据特定需求,你可以创建自己的装饰器来实现任何你想要的功能。
总之,虽然装饰器提供了很大的灵活性和便利性,但过度使用或不当使用可能会导致代码难以理解和维护。因此,在使用装饰器时,建议仔细考虑其必要性和适用性,并遵循良好的编程实践。
三、装饰器的主要用途:
装饰器(Decorator)是一种强大的Python编程工具,它允许在不修改原始函数或类代码的情况下,通过包装函数或类来扩展其功能。以下是装饰器的一些主要用途:
1、功能扩展:装饰器的主要作用之一是扩展函数或类的功能。通过在函数调用前后添加额外的逻辑,装饰器可以轻松地增加诸如日志记录、性能分析、输入验证、缓存等功能,而无需修改原始函数的代码。
2、日志记录:装饰器可以记录函数或方法的调用日志,包括参数、返回值等信息。这对于调试和追踪代码的执行过程非常有用,可以帮助开发者更好地理解程序的运行流程和性能瓶颈。
3、性能分析:通过装饰器,可以测量函数或方法的执行时间,从而进行性能分析。这对于优化代码、提高程序运行效率非常有帮助。
4、权限控制:装饰器可以用于验证用户权限,限制某些函数或类的访问权限。例如,可以通过装饰器实现基于角色的访问控制,确保只有具有特定角色的用户才能调用特定的函数或方法。
5、缓存数据:装饰器可以用于缓存函数的计算结果,避免重复计算。这对于计算密集型任务或频繁调用的函数非常有用,可以显著提高代码的执行效率。
6、代码重用和简化:装饰器可以将一些通用的、重复的代码逻辑抽离出来,使得原始函数或类的代码更加简洁和清晰。同时,由于装饰器本身也是函数或类,因此可以很容易地实现代码的重用。
7、动态修改行为:装饰器允许在运行时动态地修改对象的行为,而无需修改原始对象的代码。这使得代码更加灵活和可扩展,可以更容易地适应不同的需求和场景。
总之,虽然装饰器非常强大和灵活,但过度使用可能会导致代码变得复杂和难以维护。因此,在使用装饰器时,需要权衡其优点和缺点,并根据项目的具体需求来合理使用。
四、classmethod常用场景:
在Python中,classmethod函数的应用场景广泛,它允许我们编写与类本身关联而非与特定实例关联的方法。常用场景如下:
1、工厂方法:用于创建并返回类的实例,通常根据传入的参数以不同的方式初始化实例。
1-1、优点:
1-1-1、代码复用:工厂方法允许你使用相同的逻辑来创建类的多个实例,这有助于减少代码冗余并提高代码的可读性。
1-1-2、灵活性:工厂方法可以根据不同的输入参数返回不同的类实例,这使得代码更加灵活和可扩展。
1-1-3、封装:工厂方法将创建实例的逻辑封装在类中,使得外部代码无需知道创建实例的具体细节。这有助于隐藏实现细节,并使得API更加简洁和清晰。
1-1-4、解耦:通过将创建实例的逻辑与类的其他逻辑分开,工厂方法有助于降低代码的耦合度,使得代码更容易维护和测试。
1-2、缺点:
1-2-1、学习成本:对于初学者来说,理解类方法和工厂方法的概念可能需要一些时间。这可能会增加学习曲线,使得代码更难以理解。
1-2-2、过度使用可能导致代码复杂性:虽然工厂方法在某些情况下非常有用,但过度使用可能会导致代码变得复杂和难以维护。每个工厂方法都需要编写和维护,这可能会增加开发成本。
1-2-3、可能隐藏错误:由于工厂方法封装了创建实例的逻辑,如果这个逻辑出现问题,那么错误可能会更难被发现和调试。这要求开发者在编写工厂方法时更加谨慎,并进行充分的测试。
2、修改类级别的状态:有时你可能需要在不创建类实例的情况下修改类的状态。classmethod允许你直接通过类来访问和修改类变量。
2-1、优点:
2-1-1、集中管理:类级别的状态通常用于存储与类本身相关的信息,而不是与特定实例相关的信息。通过classmethod修改类级别的状态,可以将这些操作集中在一个地方,使得代码更加整洁和易于维护。
2-1-2、共享状态:类级别的状态在所有实例之间是共享的。这意味着,通过classmethod修改类级别的状态,可以影响所有实例的行为。这在某些情况下是非常有用的,比如当你想在多个实例之间共享某些信息时。
2-1-3、简化访问:使用classmethod可以方便地访问和修改类级别的状态,而无需创建类的实例。这在某些情况下可以简化代码,并减少不必要的对象创建。
2-2、缺点:
2-2-1、全局状态:修改类级别的状态相当于修改全局状态,这可能导致代码更难理解和测试。全局状态通常被认为是编程中的一个坏味道,因为它增加了代码的耦合度,使得不同部分的代码可能无意中相互影响。
2-2-2、线程安全问题:在多线程环境中,如果多个线程同时修改类级别的状态,可能会导致竞态条件和数据不一致的问题。因此,在使用classmethod修改类级别的状态时,需要特别注意线程安全的问题。
2-2-3、隐藏依赖:通过classmethod修改类级别的状态可能会隐藏代码之间的依赖关系。其他代码可能依赖于这些类级别的状态,而如果不了解这一点,就可能导致难以调试的问题。
2-2-4、难以扩展:如果类级别的状态变得复杂或需要频繁修改,使用classmethod可能会使代码难以扩展和维护。在这种情况下,可能需要考虑使用其他设计模式或架构来管理状态。
3、替代构造函数:有时,我们可能希望提供多个方式来创建类的实例,每个方式可能对应不同的初始化逻辑。classmethod可以作为替代构造函数,提供不同的初始化路径。
3-1、优点:
3-1-1、灵活性:使用classmethod作为工厂方法可以提供更灵活的实例创建机制。工厂方法可以根据不同的输入参数返回不同类型的实例,或者根据某些条件执行额外的初始化逻辑。
3-1-2、封装:工厂方法可以将实例创建的细节封装在类内部,外部代码无需知道创建实例的具体细节。这有助于隐藏实现细节,并提供一个简洁的API接口。
3-1-3、解耦:通过将实例创建的逻辑与类的其他逻辑分开,工厂方法有助于降低代码的耦合度。这使得代码更容易维护和测试,特别是在需要替换或扩展实例创建逻辑的情况下。
3-2、缺点:
3-2-1、学习成本:对于不熟悉工厂模式的开发者来说,使用classmethod作为工厂方法可能需要一些时间来理解和适应。这可能会增加学习曲线,并使得代码更难以阅读和理解。
3-2-2、可能增加复杂性:过度使用工厂方法可能会增加代码的复杂性。每个工厂方法都需要编写和维护,这可能会增加开发成本。此外,如果工厂方法的逻辑过于复杂,可能会导致代码难以理解和维护。
3-2-3、隐藏构造函数:使用工厂方法替代构造函数可能会隐藏类的构造函数本身。这可能会导致其他开发者不清楚如何直接创建类的实例,或者忘记了类的构造函数的存在。这可能会降低代码的可读性和可维护性。
4、实现单例模式:单例模式确保一个类只有一个实例,并提供一个全局访问点来获取该实例。classmethod在实现单例模式时非常有用。
5、注册子类或插件:当需要动态地注册或管理子类或插件时,classmethod可以用来实现这种功能。
1、classmethod函数:
1-1、Python:
# 1.函数:classmethod
# 2.功能:将一个方法转变为类方法
# 3.语法:
# class MyClass:
# @classmethod
# def my_classmethod(cls, *args, **kwargs):
# # 方法体
# pass
# 4.参数:
# 4-1、cls:表示类本身,只要不与python相关内置信息相冲突,没有硬性规定,只是为了区别实例中的“self”而取了类class前3个字母命名
# 4-2、可变参数(Variable-length Arguments):
# 4-2-1、*args(非关键字可变参数):使用*args可以在函数定义中接收任意数量的非关键字参数,这些参数在函数内部作为元组处理
# 4-2-2、**kwargs(关键字可变参数):使用**kwargs可以在函数定义中接收任意数量的关键字参数,这些参数在函数内部作为字典处理
# 5.返回值:返回值可以是任何类型的对象,包括None、整数、字符串、列表、字典等
# 6.说明:
# 6-1、接收的第一个参数是类本身:在类方法内部,你可以通过该参数来访问或修改类级别的属性或方法。
# 通常,这个参数被命名为`cls`,以区别于实例方法的`self`参数。
# 6-2、不需要类的实例来调用:类方法可以直接通过类名来调用,而不需要先创建类的实例。
# 这使得类方法非常适合作为工厂方法或用于修改类级别的状态。
# 6-3、用途:类方法常用于以下场景:
# 6-3-1、工厂方法:创建并返回类的实例,可以根据需要定制实例的初始化过程
# 6-3-2、修改类级别的状态:设置或获取与类本身关联的属性,而不是与特定实例关联的属性
# 6-3-3、替代构造函数:提供不同的方式来创建类的实例
# 6-4、与静态方法的区别:静态方法(使用@staticmethod装饰器)既不接收`self`也不接收`cls`作为第一个参数。
# 它们是完全独立的函数,只是作为类的一部分存在。静态方法通常用于组织代码或提供与类相关的实用功能。
# 7.示例:
# 应用1:工厂方法
# 定义Shape基类,它有一个抽象方法area,子类需要实现这个方法
class Shape:
def area(self):
raise NotImplementedError("子类必须实现这个方法!")
# 定义类方法create_shape,用于根据shape_type创建对应的形状对象
@classmethod
def create_shape(cls, shape_type, *args, **kwargs):
# 根据shape_type判断创建哪种形状对象
if shape_type == "circle":
return Circle(*args, **kwargs)
elif shape_type == "rectangle":
return Rectangle(*args, **kwargs)
else:
# 如果shape_type无效,则抛出异常
raise ValueError(f"无效的形状类型: {shape_type}")
# 定义Circle类,继承自Shape
class Circle(Shape):
# 初始化方法,接收半径参数
def __init__(self, radius):
self.radius = radius
# 实现area方法,计算圆的面积
def area(self):
return 3.14 * self.radius ** 2
# 定义Rectangle类,继承自Shape
class Rectangle(Shape):
# 初始化方法,接收长度和宽度参数
def __init__(self, length, width):
self.length = length
self.width = width
# 实现area方法,计算矩形的面积
def area(self):
return self.length * self.width
# 主函数入口
if __name__ == '__main__':
# 使用工厂方法create_shape创建Circle对象
circle = Shape.create_shape("circle", radius=5)
# 使用工厂方法create_shape创建Rectangle对象
rectangle = Shape.create_shape("rectangle", length=10, width=5)
# 输出圆的面积
print(circle.area())
# 输出矩形的面积
print(rectangle.area())
# 78.5
# 50
# 应用2:修改类级别的状态
class Counter:
# 初始化类变量count为0,用于记录计数值
count = 0
# 定义一个类方法,用于增加计数器
@classmethod
def increment_count(cls):
cls.count += 1
# 定义一个类方法,用于获取当前的计数值
@classmethod
def get_count(cls):
return cls.count
# 主程序入口
if __name__ == '__main__':
# 调用类方法increment_count增加计数器
Counter.increment_count()
# 打印当前的计数值
print(Counter.get_count())
# 再次调用类方法increment_count增加计数器
Counter.increment_count()
# 打印当前的计数值
print(Counter.get_count())
# 创建Counter类的两个实例
c1 = Counter()
c2 = Counter()
# 分别通过实例调用类方法get_count获取并打印计数值
# 注意:这里虽然是通过实例调用的类方法,但实际上方法内部操作的是类变量,因此所有实例共享同一个计数值
print(c1.get_count())
print(c2.get_count())
# 1
# 2
# 2
# 2
# 应用3:替代构造函数
class Person:
def __init__(self, name, age):
# 初始化方法,用于创建Person对象时设置name和age属性
self.name = name
# 使用下划线前缀表示_age为私有属性,通常用于表示该属性不应直接通过对象访问
self._age = age
@classmethod
def from_string(cls, person_str):
"""
从字符串中解析并创建Person对象
Args:
person_str(str): 包含姓名和年龄的字符串,格式为"姓名,年龄"
Returns:
Person: 创建的Person对象
"""
# 使用split方法按照逗号分隔字符串,得到姓名和年龄的列表
name, age_str = person_str.strip().split(',')
# 将年龄字符串转换为整数类型
age = int(age_str.strip())
# 使用类方法创建的类(cls)来实例化Person对象,并返回
return cls(name, age)
@property
def age(self):
# 使用property装饰器定义age属性的getter方法,使得可以像访问数据属性一样访问_age
return self._age
# 主函数
if __name__ == '__main__':
# 创建名为"Myelsa",年龄为18的Person对象
person1 = Person("Myelsa", 18)
# 创建一个包含姓名和年龄的字符串
person_str = "Jimmy, 15"
# 使用from_string类方法从字符串中解析并创建Person对象
person2 = Person.from_string(person_str)
# 打印person1的姓名和年龄
print(person1.name, person1.age)
# 打印person2的姓名和年龄
print(person2.name, person2.age)
# Myelsa 18
# Jimmy 15
# 应用4:实现单例模式
class Singleton:
# 静态类变量,用于存储单例实例
_instance = None
# 重写__new__方法,用于控制实例的创建
def __new__(cls, *args, **kwargs):
# 检查是否已经有实例存在
if cls._instance is None:
# 如果没有实例,则创建新实例
cls._instance = super().__new__(cls)
# 无论是否已有实例,都返回已存在的实例
return cls._instance
# 类方法,用于获取单例实例
@classmethod
def get_instance(cls):
# 检查是否已经有实例存在
if cls._instance is None:
# 如果没有实例,则通过调用类本身来创建实例
# 注意:这里假设__new__方法已被正确实现来确保单例
cls._instance = cls.__new__(cls)
# 返回已存在的实例
return cls._instance
# 主程序入口
if __name__ == "__main__":
# 调用get_instance方法获取单例实例
instance1 = Singleton.get_instance()
# 直接通过类调用实例化,由于__new__方法已重写,这里也会返回已存在的实例
instance2 = Singleton()
# 打印instance1和instance2是否是同一个对象
print(instance1 is instance2) # 输出: True,说明两个引用指向的是同一个对象
# True
# 应用5:注册子类或插件
class PluginBase:
# 类级别的字典,用于存储注册的插件(子类)
registered_plugins = {}
@classmethod
def register_plugin(cls, plugin_cls):
"""
注册插件(子类)到registered_plugins字典中
:param plugin_cls: 要注册的插件类
"""
# 假设每个插件都有一个唯一的名称属性
plugin_name = getattr(plugin_cls, 'name', plugin_cls.__name__)
if plugin_name in cls.registered_plugins:
raise ValueError(f"Plugin {plugin_name} is already registered.")
cls.registered_plugins[plugin_name] = plugin_cls
@classmethod
def get_plugin(cls, plugin_name):
"""
根据插件名称获取插件类
:param plugin_name: 插件名称
:return: 插件类,如果未找到则返回None
"""
return cls.registered_plugins.get(plugin_name)
# 插件类
class PluginA(PluginBase):
name = 'PluginA'
class PluginB(PluginBase):
name = 'PluginB'
if __name__ == '__main__':
# 注册插件
PluginBase.register_plugin(PluginA)
PluginBase.register_plugin(PluginB)
# 获取并打印注册的插件
for name, plugin_cls in PluginBase.registered_plugins.items():
print(f"Registered plugin: {name} -> {plugin_cls}")
# 根据名称获取插件类
plugin_a_cls = PluginBase.get_plugin('PluginA')
print(f"PluginA class: {plugin_a_cls}")
# Registered plugin: PluginA -> <class '__main__.PluginA'>
# Registered plugin: PluginB -> <class '__main__.PluginB'>
# PluginA class: <class '__main__.PluginA'>
1-2、VBA:
VBA很难模拟类似场景,略。
2、相关文章:
2-1、Python-VBA函数之旅-callable()函数
2-2、Python-VBA函数之旅-chr()函数
2-3、Python-VBA函数之旅-compile()函数
Python算法之旅:Algorithm
Python函数之旅:Functions
个人主页:非风V非雨-CSDN博客