0%

通过python的反射机制来动态创建含参数的对象

通过python的反射机制来动态创建含参数的对象

反射机制

反射机制其实就是通过字符串来获取对象的属性或者方法,或者通过字符串来创建对象。

举个例子,我们有一个类:

1
2
3
4
5
6
7
8

class A():
def __init__(self):
self.name = "A"

def print(self):
print(self.name)

我们为其成员name赋值的时候是使用self.name = XXX的形式来实现。如果我们想要通过字符串来实现这个功能,那么就可以使用反射机制:

1
2
3
4
5
6
7
8

class A():
def __init__(self):
setattr(self, "name", "A")

def print(self):
print(getattr(self, "name"))

两种写法是一样的,第二种写法是基于反射来完成的,这样就可以动态创建属性,但是IDE无法补全。

eval函数也能做到反射,但是安全性太差

反射机制动态创建类

反射机制最大的作用就是动态创建,下面以我目前遇到的一个情况为例,来阐述一下我对反射机制的应用的理解。

我目前正在写一个使用图像隐写技术来调制解调信息的项目,调制和解调制需要使用到编码器,如图。

编码器和解码器是一一对应的,编码器会将编码器本身信息和要传输的信息一起编码,因此解码器需要根据编码信息来动态创建。并且由于项目本身性质,要求存储的编码器信息尽可能少(因此不能用pickle,更何况它本身也不安全)。

我想到的方案是这样的:假设需要用的解码器是moudleA中的类decoderA,创建decoderA的参数为*args那么编码器就把moudleAdecoderA的名字以及*args存储到调制信息中,解码器在解码时,根据这些信息动态创建decoderA

这些解码后的信息都是字符串,因此需要使用反射。

1
2
3
4
5
6
7
8
9
10
11

class A():
def __init__(self):
self.name = "a"

def print(self):
print(self.name)

a = globals()["A"]()
print(a.name)

这样就实现了动态创建类。

动态创建含参数的类

但是这样远远不够,因为解码器不能能不传递参数。这时候就需要使用getattrsetattr来实现。

但是还有一个问题,因为解码后的信息都是str类型,所以类的构造函数只能接收str类型的参数,构造函数需要自己去处理这些参数。

但是如果一个类需要正确处理所有的str参数会导致代码很难维护,所以最好使用一个包装函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

def A(x:int):
# 为了防止注入,最好对A做参数检查
return _A(f'x=int({x})')

class _A():
def __init__(self, args: str) -> None:
# create 用于记录创建时的参数
self.create = args
# 把空格去掉,然后分割成一个个“key=value”的形式
args = [arg for arg in args.replace(" ", "").split(',')]

# 通过setattr来动态创建属性
for arg in args:
setattr(self, arg.split('=')[0], eval(arg.split('=')[1]))

这样通过A(10)时就会调用_A("x=int(10)")来返回一个对象。

而在_A()中,会先把这条创建参数存储下来,然后把参数分割成一个个key=value的形式,然后通过setattr来动态创建属性。

如果A本身就是一个编码解码器,并且把创建A的参数一起调制,那么在解码的时候就能正确创建出A类用于解码。

使得IDE能够补全

由于上述代码都是通过动态创建属性的,所以IDE无法补全,所以需要显示指定出参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

def A(x:int):
# 为了防止注入,最好对A做参数检查
return _A(f'x=int({x})')

class _A():
def __init__(self, args: str) -> None:
# IDE补全可用
self.x: int = None
# create 用于记录创建时的参数
self.create = args
# 把空格去掉,然后分割成一个个“key=value”的形式
args = [arg for arg in args.replace(" ", "").split(',')]

# 通过setattr来动态创建属性
for arg in args:
setattr(self, arg.split('=')[0], eval(arg.split('=')[1]))

下面给出一个完整的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

def A(kernel: int = 2):
'''
创建A类
'''
return _A(f'kernel=int({kernel})')


class _A():
'''
A类需要自己解析参数并动态创建
'''
def __init__(self, args: str) -> None:
# 保证IDE工作
self.kernel: int = None

# 动态创建参数
self.create = args
args = [arg for arg in args.replace(" ", "").split(',')]

for arg in args:
setattr(self, arg.split('=')[0], eval(arg.split('=')[1]))

def get_create(self):
return

def generate_cls(package: str,
class_name: str,
args: str):
'''
利用类名和参数来动态创建类
'''
try:
__import__(package, fromlist=True)
except:
...

return globals()[class_name](args)

a = A(10)
b = generate_cls(a.__class__.__module__,a.__class__.__name__,a.create)
print(b.kernel)

这样把a.__class__.__module__a.__class__.__name__a.create存储起来,就可以在解码时动态创建出a了。