Bootstrap

python中的反射

反射介绍

反射(Reflection)是编程语言中的一个特性,它允许程序在运行时检查、调用和修改它自己的结构和行为。这意味着程序可以在运行时访问类型信息,并且能够动态地创建对象、调用方法、访问字段和属性,甚至修改类定义。

关键点

  1. 类型检查:程序可以通过反射来发现和检查类及其成员(如字段、方法和构造函数)的元数据信息。
  2. 动态实例化:程序可以使用反射来动态创建对象实例,而不需要在编译时知道具体的类名。
  3. 方法调用:通过反射机制,程序能够动态地调用任何对象的任何方法,即使这些方法在编写代码时并不可见或已知。
  4. 属性访问:同样地,程序可以通过反射来读取或修改对象的属性值。
  5. 修改行为:某些语言支持通过反射对类定义进行修改(例如添加新方法),但这通常受限于语言本身以及运行环境是否允许此类操作。
  6. 动态代理:利用反射机制可以生成代理对象,在不改变原有代码结构的情况下增加或改变某些功能执行逻辑。
  7. 跨越模块边界:由于编译后可能丢失类型信息,使用传统方式可能无法跨模块交互。但是利用反射机制,则可以轻松实现跨模块或插件系统中类型的加载与交互。

可能导致的问题

Java 和 .NET 等高级语言都提供了强大的反射功能。然而使用反射也应该谨慎,因为它可能会导致以下问题:

  • 性能开销:相比直接代码调用,使用反射进行操作通常会有更大的性能开销。
  • 安全风险:如果不当使用或未经适当授权就允许外部输入影响到内部结构,则可能带来安全隐患。
  • 可维护性问题:过度依赖于字符串字面量等作为参数进行操作会降低代码可读性,并增加出错几率。
  • 破坏了私有性约束:正常情况下无法直接访问私有成员,但是通过反射却可以做到这一点。

python中的反射

相关概念

Python中的反射机制基于几个核心概念,这些概念共同构成了Python语言的动态特性:

  1. 动态类型(Dynamic Typing):
    Python是一种动态类型语言,意味着变量在运行时才确定其类型。这使得Python能够在运行时查询对象的类型信息。

  2. 一切皆对象(Everything is an Object):
    在Python中,所有事物都是对象。这包括函数、类、模块等。因为它们都是对象,所以它们都有属性和方法,并且可以在运行时进行检查和修改。

  3. 内省(Introspection):
    内省是指计算机程序在运行时(Run-time)检查某些事物的能力。由于Python存储了大量关于其数据和代码结构的信息,所以可以使用内置函数如type()dir()等来访问这些信息。

  4. 属性管理:
    Python允许通过内置函数如getattr()setattr()hasattr()delattr()来动态地访问和操作对象的属性。

  5. 可调用对象(Callable Objects):
    在Python中,如果一个对象实现了特殊方法__call__(),那么它就是可调用的。使用反射机制可以检查一个对象是否可调用,并且可以直接调用该对象。

  6. 元类编程(Metaclass Programming):
    元类是创建类的“类”。在Python中,你可以使用元类来控制如何创建类,并且通过反射修改或增强类定义。

  7. 描述符(Descriptors):
    描述符提供了一种协议来自定义对象属性访问逻辑。通过实现描述符协议中定义的方法(__get__(), __set__(), 和 __delete__()),你可以控制属性被访问或修改时发生什么。

  8. 鸭子类型(Duck Typing):
    鸭子类型指“如果它走路像鸭子并且叫声像鸭子,则它就可能是只鸭子”。在编程上下文中意味着一个对象有效性不取决于其继承自什么而是依赖于它具有什么样的方法或行为。利用反射机制我们不需要知道一个对象属于哪个具体类别就能够操作该对象。

  9. 异常处理
    反射操作很多情况下可能会引发异常(例如:试图获取不存在的属性),因此合理地处理异常也成为了反射编程重要组成部分。

内置函数

在Python中,反射是指代码能够访问和操作对象的内部属性和方法。Python提供了几个内置函数来实现反射功能,这些功能使得Python语言非常灵活和动态。

利用Python中提供的反射机制,我们可以在运行时动态地与程序进行交互。这种能力特别适合于需要高度灵活性和动态性质场景,例如插件系统、序列化/反序列化机制、通用函数库等。然而,在使用时也应该注意代码清晰度以及潜在性能影响。

新建一个类和一个方法便于测试

class MyClass:
    def __init__(self, value):
        self.value = value
        self.test_callable = "test_callable"

    def say_hello(self):
        return f"Hello, {self.value}"


def say_hello_outer():
    return f"say_hello_outer!"


def say_hello_outer_2(self):
    return f"say_hello_outer!"


# 创建MyClass实例
obj = MyClass("world")

type()

返回任意对象的类型。

print("type".center(180, "="))
# 获取任意对象的类型
print(type(obj))  # 输出: <class '__main__.MyClass'>
print(MyClass)  # 等同 输出: <class '__main__.MyClass'>

isinstance()

检查一个对象是否是一个已知的类型。

print("isinstance".center(180, "="))
# 检查一个对象是否是一个已知的类型
print(isinstance(obj, MyClass))  # 输出: True
print(isinstance(obj, str))  # 输出: False

getattr()

获取对象属性值。

print("getattr".center(180, "="))
# 使用getattr获取 实例的属性、方法
print(getattr(obj, "value"))  # 输出: world
print(getattr(obj, "say_hello"))  # 输出: <bound method MyClass.say_hello of <__main__.MyClass object at 0x10667dfd0>>
# print(getattr(obj, "value2"))  # 报错,不存在的属性: AttributeError: 'MyClass' object has no attribute 'value2'
print(getattr(obj, "value2", "默认值"))  # 输出: 默认值 (因为value2不存在)

# 使用getattr调用say_hello方法
method = getattr(obj, "say_hello")
# method 是个方法对象,直接加括号调用
print(method())  # 输出: Hello, world

setattr()

设置对象属性值。

print("setattr".center(180, "="))
# 使用setattr设置value属性,可以修改或者新增属性
setattr(obj, "value", "Python")  # 修改value属性
setattr(obj, "value2", "Python2")  # 新增value2属性
setattr(obj, "say_hello_outer", say_hello_outer)  # 新增方法
print(obj.value)  # 输出: Python
print(obj.value2)  # 输出: Python2
print(obj.say_hello_outer())  # 输出: say_hello_outer!

# 可以对类进行设置,这种方式是对类的所有实例都生效
setattr(MyClass, "say_hello_class", say_hello_outer)
print(MyClass.say_hello_class())

# 会报错 say_hello_outer() takes 0 positional arguments but 1 was given,因为say_hello_outer没有self参数,默认类的方法会传入self参数
# print(obj.say_hello_class())
print(dir(obj))  # 打印实例的所有属性和方法,会发现say_hello_class方法确实存在

# 重新定义个方法尝试
setattr(MyClass, "say_hello_class_2", say_hello_outer_2)
print(obj.say_hello_class_2())  # 输出: say_hello_outer! (因为say_hello_outer_2方法没有self参数,所以不会报错)

hasattr()

检查对象是否有给定名称的属性。

print("hasattr".center(180, "="))
# 使用hasattr检查say_hello方法是否存在
print(hasattr(obj, "say_hello"))  # 输出: True
print(hasattr(obj, "say_hello_class"))  # 输出: True
print(hasattr(obj, "say_hello_class_3"))  # 输出: False
print(hasattr(MyClass, "say_hello_class"))  # 输出: False

delattr()

删除对象上名为某个名称的属性。

print("delattr".center(180, "="))
# 使用delattr删除value属性,并检查其后效果
print(hasattr(obj, "value"))  # 输出: True
delattr(obj, "value")
print(hasattr(obj, "value"))  # 输出: False (因为已经被删除)

# 使用del删除value2属性,同样能实现效果
print(hasattr(obj, "value2"))  # 输出: True
del obj.value2  # 等同于 delattr(obj, "value2")
print(hasattr(obj, "value2"))  # 输出: False (因为已经被删除)
和del的区别
  1. 使用方式

    • del语句通常用于直接删除变量的引用,或者删除对象的属性。当用于删除对象属性时,你需要指定属性名称。
    • delattr函数则专门用于删除对象的属性,你需要提供对象和属性名称。
  2. 语法灵活性

    • del语句在删除属性时需要直接访问对象,这意味着你需要有一个指向该对象的变量。如果你只有一个属性的引用而没有对象的引用,那么你不能使用del语句。
    • delattr函数在这方面更加灵活,因为它接受对象和属性名称作为参数,即使你没有对象的持续引用,也可以使用它来删除属性。
  3. 异常处理

    • 当使用del删除一个不存在的属性时,Python会抛出AttributeError异常。
    • 使用delattr时,如果尝试删除不存在的属性,同样会抛出AttributeError异常。
  4. 适用场景

    • del语句更加直接和简洁,通常用于直接操作对象时删除属性。
    • delattr函数提供了更多的灵活性,特别是在你需要通过函数来操作属性删除,或者在对象的引用不是直接可用的情况下。

dir()

列出对象所有可用的属性和方法名字列表。

print("dir".center(180, "="))
# dir列出obj所有可用成员,包括内置的和自定义的属性、方法
print(dir(obj))

callable()

检查一个对象是否可以被调用(即它是否是一个函数或者定义了__call__方法)。

print("callable".center(180, "="))
# callable检查obj.say_hello是否可调用
print(callable(getattr(obj, 'say_hello')))  # 输出: True (因为它是个方法)
print(callable(getattr(obj, 'test_callable')))  # 输出:False (因为它是个属性)
# 不能检查不存在的属性,会报错

应用

动态导入模块(Dynamic Import)

动态导入模块是指在程序运行时动态地加载和使用模块,而不是在代码编写时静态导入。可以通过importlib模块来实现。这种方式可以在不知道对象名称的情况下动态获取对象。

准备导入的模块:

print("导入了本模块:base.58 测试动态导入")


class TestClass:
    value_test = "test"

    def test_method(self):
        print("test_method")


def test_method_outer(self):
    print("test_method_outer")

代码示例:

# __import__是import语句的底层实现,解释器用的
# 是Python早期用于动态导入模块的函数,而importlib模块是在Python 3中引入的,提供了更丰富的API来处理模块导入相关的操作。
# __import__("property")

import importlib
from importlib import util


def test(import_module_name):
    """
    官方推荐使用importlib.import_module("module_name")来导入模块
    :param import_module_name:
    :return:
    """
    # 如果模块不存在,会报错,可以提前判断是否存在该模块
    module_spec = importlib.util.find_spec(import_module_name)
    # 如果返回None,说明没有找到该模块
    if module_spec:
        module = importlib.import_module(import_module_name)
        print(module)
        # <module 'base.58 测试动态导入' from '/Users/fangyirui/PycharmProjects/pythonProject/base/58 测试动态导入.py'>

        # 实际应用中,最好先判断是否存在属性或方法
        print(hasattr(module, "test_method_outer"))

        func = module.test_method_outer
        print(func)
        # <function test_method_outer at 0x10b653820>

        cls = module.TestClass
        print(cls)
        # <class 'base.58 测试动态导入.TestClass'>

        # 拿到类对象后,可以实例化
        obj = cls()
        print(obj)
        print(cls.value_test)
        print(obj.value_test)
    else:
        print("导入的模块不存在!= >" + import_module_name)


test("base.58 测试动态导入")
# test("base.58 测试动态导入 2")

动态导入模块的优点是它提供了更大的灵活性,可以在运行时根据需要加载模块,而不是在编写代码时就确定。这在某些情况下非常有用,比如当模块依赖于运行时的条件或者需要动态地根据用户输入加载不同的模块时。

使用type()动态创建类

type()不仅用于返回一个对象的类型,还可以作为动态创建类的工具。当type()被调用时,它可以接受三个参数:

  1. 类名(字符串形式)
  2. 一个包含父类的元组(对于继承)
  3. 一个包含属性名与值或者方法名称与函数之间映射关系的字典

语法结构如下:

MyClass = type('MyClass', (BaseClass,), {'attribute1': value1, 'method1': function1})

这里,'MyClass'是新创建的类名;(BaseClass,)定义了该类所继承的父类;最后一个参数是一个字典,定义了该类内部应有的属性和方法。

假设我们想要创建一个简单的动物园管理系统,其中包含动物的类别。每种动物都有一些共同的属性,比如名字和年龄,但是也有一些独特的行为,比如它们各自的叫声。

# 定义一个基本的动物类
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        return "动物发出声音。。。"


# 使用type()函数动态创建狗类
Dog = type('Dog', (Animal,), {'speak': lambda self: f"{self.name} 叫声:汪汪"})

# 使用type()函数动态创建猫类
Cat = type('Cat', (Animal,), {'speak': lambda self: f"{self.name} 叫声:喵喵"})

# 创建狗的实例
fido = Dog("二狗", 5)
print(fido.speak())  # 输出: 二狗 叫声:汪汪

# 创建猫的实例
whiskers = Cat("小花", 3)
print(whiskers.speak())  # 输出: 小花 叫声:喵喵

通过这种方式,我们不需要为每种动物编写一个单独的类定义。相反,我们可以在运行时根据需要动态创建新的类,并赋予它们特定的行为。这种方法在需要灵活地扩展程序功能时非常有用。

;