通过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无法补全。
反射机制动态创建类
反射机制最大的作用就是动态创建,下面以我目前遇到的一个情况为例,来阐述一下我对反射机制的应用的理解。
我目前正在写一个使用图像隐写技术来调制解调信息的项目,调制和解调制需要使用到编码器,如图。
编码器和解码器是一一对应的,编码器会将编码器本身信息和要传输的信息一起编码,因此解码器需要根据编码信息来动态创建。并且由于项目本身性质,要求存储的编码器信息尽可能少(因此不能用pickle,更何况它本身也不安全)。
我想到的方案是这样的:假设需要用的解码器是moudleA
中的类decoderA
,创建decoderA
的参数为*args
,那么编码器就把moudleA
和decoderA
的名字以及*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)
|
这样就实现了动态创建类。
动态创建含参数的类
但是这样远远不够,因为解码器不能能不传递参数。这时候就需要使用getattr
和setattr
来实现。
但是还有一个问题,因为解码后的信息都是str
类型,所以类的构造函数只能接收str
类型的参数,构造函数需要自己去处理这些参数。
但是如果一个类需要正确处理所有的str
参数会导致代码很难维护,所以最好使用一个包装函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def A(x:int): return _A(f'x=int({x})')
class _A(): def __init__(self, args: str) -> 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]))
|
这样通过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): return _A(f'x=int({x})')
class _A(): def __init__(self, args: str) -> None: self.x: 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]))
|
下面给出一个完整的例子:
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: 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
了。