1、何为反射
1.1、概念
反射(Reflection)是计算机科学中的一个术语,指的是一种在运行时动态地获取、操作和修改一个语言的特定对象的能力。在编程中,反射可以让程序在运行时动态地获取类的信息,包括类的属性、方法和构造函数等,而不需要在编译时确定。
使用反射,程序可以在运行时动态地创建一个对象、调用对象的方法、访问对象的属性并修改它们的值、获取对象所属的类的信息等。这种动态性使得程序能够根据运行时的条件来决定如何处理对象,从而增加了程序的灵活性和可扩展性。
在许多编程语言中都提供了反射的机制,例如Ja、 Go、C#和Python等。具体的实现方式和使用方法可能会有所差异,但核心的概念和作用是相似的。
1.2、用途
以下是一些编程反射的常见用途:
-
动态实例化对象:通过反射可以根据类名字符串实例化对象,而不需要提前知道类的具体类型。这对于需要根据配置文件或用户输入来动态创建对象非常有用。
-
动态调用方法:通过反射可以在运行时动态调用类的方法,包括私有方法。这对于需要根据运行时条件选择不同的方法执行逻辑非常有用。
-
动态访问字段:通过反射可以在运行时动态获取和修改类的字段值,包括私有字段。这对于需要在运行时获取和修改对象的属性非常有用。
-
获取类的信息:通过反射可以获取类的各种元数据信息,如类名、父类、实现的接口、构造方法、方法、字段等。这对于开发一些通用的工具和框架非常有用。
-
注解处理:通过反射可以获取类、方法、字段上的注解信息,并动态根据注解做一些处理,如生成文档、验证参数等。
-
动态代理:通过反射可以创建动态代理对象,用于在不修改现有类的情况下增强类的行为。
总体而来,静态类型的编程语言,例如java,反射API的能力要强于动态语言。
2、java反射案例
我们先来看看java版本的反射。假设我们有一个汽车类。该类的属性与方法都是私有的。正常是无法调用的。
java
public class Car {
private static int output;
private String brand;
private int year;
public Car() {
}
public Car(String brand, int year) {
this.brand = brand;
this.year = year;
}
private void drive() {
System.out.println("Driving the car...");
}
public static int getOutput(){
return output;
}
}
使用反射,我们是可以实现动态创造类示例,调用私有方法,调用私有方法。
java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectExample {
public static void main(String[] args) {
try {
// 获取Class对象
Class<?> carClass = Class.forName("Car");
// 实例化对象
Constructor<?> constructor = carClass.getConstructor(String.class, int.class);
Object carObject = constructor.newInstance("BMW", 2024);
// 调用私有方法
Method driveMethod = carClass.getMethod("drive");
driveMethod.setAccessible(true);
driveMethod.invoke(carObject);
// 访问私有字段
Field brandField = carClass.getDeclaredField("brand");
brandField.setAccessible(true);
String brand = (String) brandField.get(carObject);
System.out.println("Car brand: " + brand);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们也可以查看所有的属性及方法(包括静态以及示例)
java
public static void main(String[] args) {
Class<Car> clazz = Car.class;
List<String> instanceFields = new ArrayList<>();
List<String> staticFields = new ArrayList<>();
List<String> instanceMethods = new ArrayList<>();
List<String> staticMethods = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
staticFields.add(field.getName());
} else {
instanceFields.add(field.getName());
}
}
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isStatic(method.getModifiers())) {
staticMethods.add(method.getName());
} else {
instanceMethods.add(method.getName());
}
}
System.out.println("所有静态属性");
System.out.println(staticFields); //[output]
System.out.println("所有实例属性");
System.out.println(instanceFields); //[brand, year]
System.out.println("所有静态方法");
System.out.println(staticMethods); //[getOutput]
System.out.println("所有实例方法");
System.out.println(instanceMethods); //[drive]
}
3、Python反射
3.1、反射API
Python中的反射API主要集中在hasattr
、getattr
和setattr
这三个内置函数,以及delattr
函数等。
需要注意的是,API涉及属性与方法时,不区分属性和方法(虽然英文单词表示属性,python类内部属性与方法在同一个dict),而不像java那样,属性与方法区别对应。
|-------------------------------|----------------------------------|
| hasattr
(object, name) | 判断对象是否具有某个属性或方法 |
| getattr
(object, name) | 获取对象的某个属性或方法 |
| setattr
(object, name,value) | 设置对象的某个属性或方法 |
| delattr
(object, name) | 删除对象的某个属性或方法 |
| dir(obj) | 返回对象obj的所有属性和方法的名称列表 |
| type(obj): | 返回对象obj的类型 |
| inspect模块 | 提供了更高级的反射功能,可以获取对象的签名、参数、源代码等信息。 |
3.2、反射案例
python
class MyClass:
def __init__(self):
self.x = 10
self.y = 20
def my_method(self):
print("Hello, reflection!")
def my_method2(self, word):
print("Hello!", word)
obj = MyClass()
# 使用getattr获取属性值
print(getattr(obj, 'x')) # 输出: 10
# 使用setattr设置属性值
setattr(obj, 'x', 20)
print(obj.x) # 输出: 20
# 使用hasattr检查属性是否存在
print(hasattr(obj, 'x')) # 输出: True
print(hasattr(obj, 'y')) # 输出: False
# 使用dir获取对象的所有属性和方法 (屏蔽内置属性)
attrs = [x for x in dir(obj) if not x.startswith('__')]
print(attrs)
# 使用type获取对象的类型
print(type(obj)) # 输出: <class '__main__.MyClass'>
# 使用inspect模块获取对象的信息
import inspect
# 获取方法签名
method_sig = inspect.signature(obj.my_method2)
print(method_sig) # 输出: (word)
# 获取方法的参数名称
method_params = method_sig.parameters
print([param for param in method_params]) # 输出: ['word']
# 获取方法的源代码
method_source = inspect.getsource(obj.my_method)
print(method_source) # 输出: def my_method(self):\n print("Hello, reflection!")
如果dir()方法的参数是一个class的话,返回所有实例方法、静态方法,静态属性;如果dir()方法的参数是一个对象的话,返回所有实例方法、静态方法,静态属性,还有加上所有实例属性(构造函数内部申明的属性)。
python
class MyClass:
staticField = 100
def __init__(self):
self.x = 10
self.y = 20
def my_method(self):
print("Hello, reflection!")
@staticmethod
def my_method2(self, word):
print("Hello!", word)
obj = MyClass()
static_attrs = [x for x in dir(MyClass) if not x.startswith('__')]
print(static_attrs)
# 使用dir获取对象的所有属性和方法 (屏蔽内置属性)
attrs = [x for x in dir(obj) if not x.startswith('__')]
print(attrs)
3.3、与java的比较
3.3.1、访问实例属性的区别
java作为一门静态语言,在编译阶段已经确定了类的结构(包括静态方法,实例方法,静态属性,实例属性,即使热更新也无法改变类的结构)。因此,java可以直接通过类获取所有字段,见上一节java反射案例。
而python只有通过dir()方法传入对象参数的时候,才可以获取所有的字段(因为python允许运行期动态增减字段)。
3.3.2、通过构造函数反射生成实例
java允许反射获取class的指定构造函数,并动态创建实例。比较常见的做法是,每个类提供一个无参构造函数,根据反射依次获取所有字段的类型,动态设置。
java
public class Main {
public static void main(String[] args) throws Exception{
Class<?> carClass = Class.forName("Car");
// 指定有参构造函数
Constructor<?> constructor = carClass.getConstructor(String.class, int.class);
Object car1 = constructor.newInstance("BMW", 2024);
// 无参构造函数
Object car2 = Class.forName("Car").newInstance();
}
}
python实现同样的功能,道路显得有点曲折。无参构造函数内部申明所有的实例属性,反射创建无状态实例,通过dir()方法获取所有字段,再通过setattr()动态设值。
python
class MyClass:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f"Hello, {self.name}!")
# 要创建一个类的实例,可以使用以下代码:
class_name = "MyClass"
args = ("John",)
# 使用globals()函数来获取全局命名空间中的类,并使用type()函数来创建实例对象
if class_name in globals():
class_object = globals()[class_name]
instance = type(class_name)(*args)
instance.say_hello() # 输出: Hello, John!
else:
print(f"Class {class_name} not found")