通过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了。