24.反射和动态属性

发布时间 2023-12-05 21:22:50作者: 贝壳里的星海

反射和动态属性

概念

反射是指通过一组内置的函数和语句,在运行时修改对象的能力。

允许动态创建类、调用方法、获取和设置属性,以及修改对象的行为。

class MyClass:
    pass

DyClass = type("DyClass", (), {})  # 动态创建类
my_instance = DyClass()  # 创建类的实例

主要场景

  1. 动态加载模块和类:使用反射可以在运行时动态加载模块和类,以便于程序更加灵活和可扩展。
  2. 动态修改对象属性和方法:使用反射可以在运行时动态修改对象的属性和方法,以便于程序更加灵活。
  3. 实现插件系统:使用反射可以实现插件系统,允许程序在运行时动态加载和卸载插件。
  4. 实现ORM框架:使用反射可以实现ORM框架,允许程序在运行时动态地将Python对象映射到数据库中的表格。

属性和方法

  • hasattr(object,'attrName'):判断该对象是否有指定名字的属性或方法,返回值是bool类型
  • getattr(object,'attrName'):获取对象指定名称的属性或方法,返回值是str类型
  • setattr(object,'attrName',value):给指定的对象添加属性以及属性值,可以通过对象、字符串和值的方式传递
  • delattr(object,'attrName'):删除对象指定名称的属性或方法值,无返回值
  • dir():获取对象的所有属性和方法的列表。
  • type():获取对象的类型。

动态创建导入

# 动态创建对象
class MyClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

my_class = type("MyClass", (), {"x": 1, "y": 2})
my_object = my_class()
print(my_object.x, my_object.y)  # 输出 1 2
# 动态调用模块中的函数
# 使用 importlib.import_module() 导入模块
import importlib
module_name = 'math'
module = importlib.import_module(module_name)

# 使用 getattr() 访问模块中的函数
sqrt_function = getattr(module, 'sqrt')
result = sqrt_function(4)
print(result)  # 输出: 2.0

调用和访问

# 访问对象属性
class MyClass:
    def __init__(self, x):
        self.x = x

obj = MyClass(42)
attr_name = "x"
attr_value = getattr(obj, attr_name)
print(f"{attr_name} = {attr_value}")
# 动态调用对象方法
class MyClass:
    def my_method(self, x, y):
        return x + y

my_object = MyClass()
result = getattr(my_object, "my_method")(1, 2)
print(result)  # 输出 3
# 动态导入模块
# 使用 importlib.import_module() 导入模块
import importlib
module_name = 'math'
module = importlib.import_module(module_name)

# 使用 getattr() 访问模块的属性
pi_value = getattr(module, 'pi')
print(pi_value)  # 输出: 3.141592653589793
# 获取类属性
class MyClass:
    my_class_attribute = "Hello World"

print(getattr(MyClass, "my_class_attribute"))  # 输出 "Hello World"

检查对象属性

# 检查对象是否具有属性
class MyClass:
    def __init__(self):
        self.my_attribute = "Hello World"

my_object = MyClass()
print(hasattr(my_object, "my_attribute"))  # 输出 True
print(hasattr(my_object, "non_existent_attribute"))  # 输出 False
# 动态获取类的方法列表
class MyClass:
    def __init__(self):
        self.my_attribute = 'Hello, World!'
        
    def my_method(self):
        print(self.my_attribute)

# 使用 dir() 获取类的方法列表
method_list = [method_name for method_name in dir(MyClass) if callable(getattr(MyClass, method_name))]
print(method_list)  # 输出: ['__init__', '__module__', 'my_method']

动态修改属性

# 动态修改对象的属性
class MyClass:
    def __init__(self):
        self.my_attribute = 'Hello, World!'

my_object = MyClass()

# 使用 setattr() 修改对象的属性
setattr(my_object, 'my_attribute', 'Hello, Universe!')
print(my_object.my_attribute)  # 输出: 'Hello, Universe!'

inspect模块

inspect 模块提供了一些有用的函数帮助获取对象的信息,例如模块、类、方法、函数、回溯、帧对象以及代码对象。

inspect 模块主要提供了四种功能

  • (1) 对是否是模块,框架,函数等进行类型检查。
  • (2) 获取源码
  • (3) 获取类或函数的参数的信息
  • (4) 解析堆栈

inspect.getmembers 函数用于返回一个对象上的所有成员,其返回值是一个键值对为元素的列表。

函数 定义 功能
inspect.ismodule inspect.ismodule(object) 当该对象是一个模块时返回 True。
inspect.isclass inspect.isclass(object) 当该对象是一个类时返回 True,无论是内置类或者 Python 代码中定义的类。
inspect.ismethod inspect.ismethod(object) 当该对象是一个 Python 写成的绑定方法时返回 True。
inspect.isfunction inspect.isfunction(object) 当该对象是一个 Python 函数时(包括使用 lambda 表达式创造的函数),返回 True。
import inspect

class TestClass():

    def __init__(self):
        self.data = {}
        self.path = ""

    def test(self):
        pass

# print(inspect.getmembers(TestClass))   # 获取所有成员,包含了属性和函数

# 可选参数 predicate 可以筛选指定的成员
print(inspect.getmembers(
    TestClass, 
    predicate = lambda obj: inspect.isfunction(obj) and obj.__name__.startswith('test') 
))
[('test', <function TestClass.test at 0x000002A051F62790>)]

官方文档 https://docs.python.org/zh-cn/3/library/inspect.html

实战-插件调用

# 插件接口定义
class PluginInterface:
    def run(self):
        pass
 
# 插件实现类1
class Plugin1(PluginInterface):
    def run(self):
        print("Running Plugin 1")
 
# 插件实现类2
class Plugin2(PluginInterface):
    def run(self):
        print("Running Plugin 2")
 
# 插件管理器
class PluginManager:
    def __init__(self):
        self.plugins = {}
 
    def load_plugins(self, plugin_module_names):
        for module_name in plugin_module_names:
            try:
                # 动态加载模块
                module = __import__(module_name)
                
                # 遍历模块的成员
                for member_name in dir(module):
                    member = getattr(module, member_name)
                    
                    # 检查成员是否为插件实现类
                    if (
                        isinstance(member, type)
                        and issubclass(member, PluginInterface)
                        and member != PluginInterface
                    ):
                        # 创建插件实例
                        plugin = member()
                        
                        # 注册插件
                        self.plugins[module_name] = plugin
            except ImportError:
                print(f"无法加载模块:{module_name}")
 
    def run_plugins(self):
        for module_name, plugin in self.plugins.items():
            print(f"Running plugin from module: {module_name}")
            plugin.run()
 
# 主程序
if __name__ == "__main__":
    # 创建插件管理器
    manager = PluginManager()
 
    # 加载插件模块
    manager.load_plugins(["plugin1", "plugin2"])
 
    # 运行插件
    manager.run_plugins()

动态代码生成

import inspect

class Extracter(object):

    def extract(self, data):
        return {
            extract_method_name.split("_", 1)[-1]: extract_method(data)
            for extract_method_name, extract_method in inspect.getmembers(self, inspect.ismethod)
            if extract_method_name.startswith("extract_")
        }

    def extract_title(self, data):
        return f"{data}-title"
    
    def extract_context(self, data):
        return f"{data}-context"
    
    def extract_pubdate(self, data):
        return f"{data}-pubdate"
    

data = Extracter().extract("text")
print(data)
# {'title': 'text-title', 'context': 'text-context', 'pubdate': 'text-pubdate'}


简单工程模式

from abc import ABCMeta, abstractmethod
# 产品基类
class Product(metaclass=ABCMeta):
    
    @abstractmethod
    def use(self):
        pass
    
# 产品A
class ConcreteProductA(Product):
    """A"""
    def __init__(self) -> None:
        super().__init__()
        pass

    def use(self):
        print(f'[{self.__class__.__name__}]: use')
    
    @classmethod
    def doc(cls):
        return 'A'

# 产品B
class ConcreteProductB(Product):
    """B"""
    def __init__(self) -> None:
        super().__init__()
        pass
    def use(self):
        print(f'[{self.__class__.__name__}]: use')
    @classmethod
    def doc(cls):
        return 'B'

print([_cls.doc() for _cls in Product.__subclasses__()])



class Factory(object):

    @staticmethod
    def create(product_name):
        # 构建映射表
        products = {_cls.doc(): _cls for _cls in Product.__subclasses__() if _cls.doc and _cls.__name__ != "Product"}
        print(products)
        return products[product_name]()


A=Factory.create("A")
A.use()
# [ConcreteProductA]: use
B=Factory.create("B")
B.use()
# [ConcreteProductB]: use

参考资料

https://mp.weixin.qq.com/s/NRSkzdAbcdF828YhFd88NA

https://juejin.cn/post/7301579823414607898

https://juejin.cn/post/7280439887966863360?searchId=2023120410572214AADC9B1A87571DF03D#heading-5 (工厂模式)