0x00 前言
在Python中,类也是作为一种对象存在的,因此可以在运行时动态创建类,这也是Python灵活性的一种体现。
本文介绍了如何使用type动态创建类,以及相关的一些使用方法与技巧。
0x01 类的本质
何为类?类是对现实生活中一类具有共同特征的事物的抽象,它描述了所创建的对象共同的属性和方法。在常见的编译型语言(如C++)中,类在编译的时候就已经确定了,运行时是无法动态创建的。那么Python是如何做到的呢?
来看下面这段代码:
class A(object):passprint(A)print(A.__class__) COPY
在Python2中执行结果如下:
<class '__main__.A'><type 'type'>COPY
在Python3中执行结果如下:
<class '__main__.A'><class 'type'>COPY
可以看出,类A的类型是type,也就是说:type实例化后是类,类实例化后是对象。
0x02 使用type动态创建类
type的参数定义如下:
type(name, bases, dict)
name: 生成的类名bases: 生成的类基类列表,类型为tupledict: 生成的类中包含的属性或方法
例如:可以使用以下方法创建一个类A
cls = type('A', (object,), {'__doc__': 'class created by type'})print(cls)print(cls.__doc__)COPY
输出结果如下:
<class '__main__.A'>class created by typeCOPY
可以看出,这样创建的类与静态定义的类基本没有什么差别,使用上还更灵活。
这种方法的使用场景之一是:
有些地方需要传入一个类作为参数,但是类中会用到某些受外界影响的变量;虽然使用全局变量可以解决这个问题,但是比较丑陋。此时,就可以使用这种方法动态创建一个类来使用。
以下是一个使用的示例:
import sockettry:import SocketServerexcept ImportError:# python3import socketserver as SocketServerclass PortForwardingRequestHandler(SocketServer.BaseRequestHandler):'''处理端口转发请求'''def handle(self):sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.connect(self.server) # self.server是在动态创建类时传入的# 连接目标服务器,并转发数据# 以下代码省略...def gen_cls(server):'''动态创建子类'''return type('%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()COPY
在上面的例子中,由于目标服务器地址是由用户传入的,而PortForwardingRequestHandler类的实例化是在ThreadingTCPServer里实现的,我们没法控制。因此,使用动态创建类的方法可以很好地解决这个问题。
0x03 使用元类(metaclass)
类是实例的模版,而元类是类的模版。通过元类可以创建出类,类的默认元类是type,所有元类必须是type的子类。
下面是元类的一个例子:
import structclass MetaClass(type):def __init__(cls, name, bases, attrd):super(MetaClass, cls).__init__(name, bases, attrd)def __mul__(self, num):return type('%s_Array_%d' % (self.__name__, num), (ArrayTypeBase,), {'obj_type': self, 'array_size': num, 'size': self.size * num})class IntTypeBase(object):'''类型基类'''__metaclass__ = MetaClasssize = 0format = '' # strcut格式def __init__(self, val=0):if isinstance(val, str): val = int(val)if not isinstance(val, int):raise TypeError('类型错误:%s' % type(val))self._net_order = True # 默认存储的为网络序数据self.value = valself._num = 1def __str__(self):return '%d(%s)' % (self._val, self.__class__.__name__)def __cmp__(self, val):if isinstance(val, IntTypeBase):return cmp(self.value, val.value)elif isinstance(val, (int, long)):return cmp(self.value, val)elif isinstance(val, type(None)):return cmp(int(self.value), None)else:raise TypeError('类型错误:%s' % type(val))def __int__(self):return int(self.value)def __hex__(self):return hex(self.value)def __index__(self):return self.valuedef __add__(self, val):return int(self.value + val)def __radd__(self, val):return int(val + self.value)def __sub__(self, val):return self.value - valdef __rsub__(self, val):return val - self.valuedef __mul__(self, val):return self.value * valdef __div__(self, val):return self.value / valdef __mod__(self, val):return self.value % valdef __rshift__(self, val):return self.value >> valdef __and__(self, val):return self.value & val@propertydef net_order(self):return self._net_order@net_order.setterdef net_order(self, _net_order):self._net_order = _net_order@propertydef value(self):return self._val@value.setterdef value(self, val):if not isinstance(val, int):raise TypeError('类型错误:%s' % type(val))if val < 0: raise ValueError(val)max_val = 256 ** (self.size) - 1if val > max_val: raise ValueError('%d超过最大大小%d' % (val, max_val))self._val = valdef unpack(self, buff, net_order=True):'''从buffer中提取出数据'''if len(buff) < self.size: raise ValueError(repr(buff))buff = buff[:self.size]fmt = self.formatif not net_order: fmt = '<' + fmt[1]self._val = struct.unpack(fmt, buff)[0]return self._valdef pack(self, net_order=True):'''返回内存数据'''fmt = self.formatif not net_order: fmt = '<' + fmt[1]return struct.pack(fmt, self._val)@staticmethoddef cls_from_size(size):'''从整型大小返回对应的类'''if size == 1:return c_uint8elif size == 2:return c_uint16elif size == 4:return c_uint32elif size == 8:return c_uint64else:raise RuntimeError('不支持的整型数据长度:%d' % size)@classmethoddef unpack_from(cls, str, net_order=True):obj = cls()obj.unpack(str, net_order)return int(obj)class ArrayTypeBase(object):'''数组类型基类'''def __init__(self, val=''):init_val = 0if isinstance(val, int):init_val = valelse:val = str(val)self._obj_array = [self.obj_type(init_val) for _ in range(self.array_size)] # 初始化self.value = valdef __str__(self):return str(self.value)def __repr__(self):return repr(self.value)def __getitem__(self, idx):return self._obj_array[idx].valuedef __setitem__(self, idx, val):self._obj_array[idx].value = valdef __getslice__(self, i, j):result = [obj.value for obj in self._obj_array[i:j]]if self.obj_type == c_ubyte:result = [chr(val) for val in result]result = ''.join(result)return resultdef __add__(self, oval):if not isinstance(oval, str):raise NotImplementedError('%s还不支持%s类型' % (self.__class__.__name__, type(oval)))return self.value + ovaldef __radd__(self, oval):return oval + self.valuedef __iter__(self):'''迭代器'''for i in range(self.length):yield self[i]@propertydef value(self):result = [obj.value for obj in self._obj_array]if self.obj_type == c_ubyte:result = [chr(val) for val in result]result = ''.join(result)return result@value.setterdef value(self, val):if isinstance(val, list):raise NotImplementedError('ArrayType还不支持list')elif isinstance(val, str):self.unpack(val)def unpack(self, buff, net_order=True):''''''if len(buff) == 0: returnif len(buff) < self.size: raise ValueError('unpack数据长度错误:%d %d' % (len(buff), self.size))for i in range(self.array_size):self._obj_array[i].unpack(buff[i * self.obj_type.size:], net_order)def pack(self, net_order=True):''''''result = ''for i in range(self.array_size):result += self._obj_array[i].pack()return resultclass c_uint8(IntTypeBase):'''unsigned char'''size = 1format = '!B'class c_uint16(IntTypeBase):'''unsigned short'''size = 2format = '!H'class c_uint32(IntTypeBase):'''unsigned int32'''size = 4format = '!I'class c_uint64(IntTypeBase):'''unsigned int64'''size = 8format = '!Q'cls = c_ubyte * 5print(cls)val = cls(65)print(val)COPY
以上代码在Python2.7中输出结果如下:
<class '__main__.c_ubyte_Array_5'>AAAAA COPY
在Python3中,metaclass的定义方法做了修改,变成了:
class IntTypeBase(object, metaclass=MetaClass):pass COPY
为了兼容性。可以使用six库中的方法:
import six@six.add_metaclass(MetaClass)class IntTypeBase(object):pass COPY
使用元类的优点是可以使用更加优雅的方式创建类,如上面的c_ubyte * 5,提升了代码可读性和技巧性。
0x04 重写__new__方法
每个继承自object的类都有__new__方法,这是个在类实例化时优先调用的方法,时机早于__init__。它返回的类型决定了最终创建出来的对象的类型。
请看以下代码:
class A(object):def __new__(self, *args, **kwargs):return B()class B(object):passa = A()print(a) COPY
输出结果如下:
<__main__.B object at 0x023576D0>COPY
可以看到,明明实例化的是A,但是返回的对象类型却是B,这里主要就是__new__在起作用。
下面的例子展示了在__new__中动态创建类的过程:
class B(object):def __init__(self, var):self._var = vardef test(self):print(self._var)class A(object):def __new__(self, *args, **kwargs):if len(args) == 1 and isinstance(args[0], type):return type('%s_%s' % (self.__name__, args[0].__name__), (self, args[0]), {})else:return object.__new__(self, *args, **kwargs)def output(self):print('output from new class %s' % self.__class__.__name__)obj = A(B)('Hello World')obj.test()obj.output() COPY
结果输出如下:
Hello Worldoutput from new class A_BCOPY
这个例子实现了动态创建两个类的子类,比较适合存在很多类需要排列组合生成N多子类的场景,可以避免要写一堆子类代码的痛苦。
0x05 总结
动态创建类必须要使用type实现,但是,根据不同的使用场景,可以选择不同的使用方法。
这样做对静态分析工具其实是不友好的,因为在运行过程中类型发生了变化。而且,这也会降低代码的可读性,一般情况下也不推荐用户使用这样存在一定技巧性的代码。
Be the first guy leaving a comment!