Python中动态创建类的方法

0x00 前言

在Python中,类也是作为一种对象存在的,因此可以在运行时动态创建类,这也是Python灵活性的一种体现。

本文介绍了如何使用动态创建类,以及相关的一些使用方法与技巧。

0x01 类的本质

何为类?类是对现实生活中一类具有共同特征的事物的抽象,它描述了所创建的对象共同的属性和方法。在常见的编译型语言(如)中,类在编译的时候就已经确定了,运行时是无法动态创建的。那么Python是如何做到的呢?

来看下面这段代码:

classA(object):passprint(A)print(A.__class__)

在Python2中执行结果如下:

在Python3中执行结果如下:

可以看出,类的类型是,也就是说:实例化后是,实例化后是。

0x02 使用动态创建类

的参数定义如下:

type(name, bases, dict)

: 生成的类名

: 生成的类基类列表,类型为tuple

: 生成的类中包含的属性或方法

例如:可以使用以下方法创建一个类

cls=type('A',(object,),{'__doc__':'class created by type'})print(cls)print(cls.__doc__)

输出结果如下:

classcreatedby type

可以看出,这样创建的类与静态定义的类基本没有什么差别,使用上还更灵活。

这种方法的使用场景之一是:

有些地方需要传入一个类作为参数,但是类中会用到某些受外界影响的变量;虽然使用全局变量可以解决这个问题,但是比较丑陋。此时,就可以使用这种方法动态创建一个类来使用。

以下是一个使用的示例:

importsockettry:importSocketServerexcept ImportError:# python3importsocketserverasSocketServerclassPortForwardingRequestHandler(SocketServer.BaseRequestHandler):'''处理端口转发请求''' defhandle(self):sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)sock.connect(self.server)# self.server是在动态创建类时传入的 # 连接目标服务器,并转发数据 # 以下代码省略...defgen_cls(server):'''动态创建子类'''returntype('%s_%s'%(ProxyRequestHandler.__name__,server),(PortForwardingRequestHandler,object),{'server':server})server=SocketServer.ThreadingTCPServer(('127.0.0.1',8080),gen_cls(('www.qq.com',80)))server.serve_forever()

在上面的例子中,由于目标服务器地址是由用户传入的,而类的实例化是在里实现的,我们没法控制。因此,使用动态创建类的方法可以很好地解决这个问题。

0x03 使用元类()

类是实例的模版,而元类是类的模版。通过元类可以创建出类,类的默认元类是,所有元类必须是的子类。

下面是元类的一个例子:

importstructclassMetaClass(type):def__init__(cls,name,bases,attrd):super(MetaClass,cls).__init__(name,bases,attrd)def__mul__(self,num):returntype('%s_Array_%d'%(self.__name__,num),(ArrayTypeBase,),{'obj_type':self,'array_size':num,'size':self.size*num})classIntTypeBase(object):'''类型基类''' __metaclass__=MetaClass size=format=''# strcut格式 def__init__(self,val=):ifisinstance(val,str):val=int(val)ifnotisinstance(val,int):raiseTypeError('类型错误:%s'%type(val))self._net_order=True # 默认存储的为网络序数据 self.value=val self._num=1def__str__(self):return'%d(%s)'%(self._val,self.__class__.__name__)def__cmp__(self,val):ifisinstance(val,IntTypeBase):returncmp(self.value,val.value)elifisinstance(val,(int,long)):returncmp(self.value,val)elifisinstance(val,type(None)):returncmp(int(self.value),None)else:raiseTypeError('类型错误:%s'%type(val))def__int__(self):returnint(self.value)def__hex__(self):returnhex(self.value)def__index__(self):returnself.value def__add__(self,val):returnint(self.value+val)def__radd__(self,val):returnint(val+self.value)def__sub__(self,val):returnself.value-val def__rsub__(self,val):returnval-self.value def__mul__(self,val):returnself.value*val def__div__(self,val):returnself.value/val def__mod__(self,val):returnself.value%val def__rshift__(self,val):returnself.value>>val def__and__(self,val):returnself.value&val @property defnet_order(self):returnself._net_order @net_order.setter defnet_order(self,_net_order):self._net_order=_net_order @property defvalue(self):returnself._val @value.setter defvalue(self,val):ifnotisinstance(val,int):raiseTypeError('类型错误:%s'%type(val))ifvalmax_val:raiseValueError('%d超过最大大小%d'%(val,max_val))self._val=val defunpack(self,buff,net_order=True):'''从buffer中提取出数据'''iflen(buff)

以上代码在Python2.7中输出结果如下:

AAAAA

在Python3中,的定义方法做了修改,变成了:

classIntTypeBase(object,metaclass=MetaClass):pass

为了兼容性。可以使用库中的方法:

使用元类的优点是可以使用更加优雅的方式创建类,如上面的,提升了代码可读性和技巧性。

0x04 重写方法

每个继承自的类都有方法,这是个在类实例化时优先调用的方法,时机早于。它返回的类型决定了最终创建出来的对象的类型。

请看以下代码:

classA(object):def__new__(self,*args,**kwargs):returnB()classB(object):passa=A()print(a)

输出结果如下:

可以看到,明明实例化的是,但是返回的对象类型却是,这里主要就是在起作用。

下面的例子展示了在中动态创建类的过程:

classB(object):def__init__(self,var):self._var=vardeftest(self):print(self._var)classA(object):def__new__(self,*args,**kwargs):iflen(args)==1andisinstance(args[],type):returntype('%s_%s'%(self.__name__,args[].__name__),(self,args[]),{})else:returnobject.__new__(self,*args,**kwargs)defoutput(self):print('output from new class %s'%self.__class__.__name__)obj=A(B)('Hello World')obj.test()obj.output()

结果输出如下:

Hello WorldoutputfromnewclassA_B

这个例子实现了动态创建两个类的子类,比较适合存在很多类需要排列组合生成N多子类的场景,可以避免要写一堆子类代码的痛苦。

0x05 总结

动态创建类必须要使用实现,但是,根据不同的使用场景,可以选择不同的使用方法。

这样做对静态分析工具其实是不友好的,因为在运行过程中类型发生了变化。而且,这也会降低代码的可读性,一般情况下也不推荐用户使用这样存在一定技巧性的代码。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190208A00D6U00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励