如何在Python中动态创建类
0x00 简介
Python中的类作为对象存在,因此您可以在运行时动态创建类,这也显示了Python的灵活性。
我将在本文中介绍如何用 type
动态创建类以及一些相关的用法和技巧。
0x01 类
什么是类?类是一种在现实生活中具有共同特征的事物的抽象,它描述了正在创建的对象共有的属性和方法。在常见的编译语言(例如C ++)中,类是在编译时定义的,不能在运行时动态创建。那么Python是如何做到的呢?
看下面的代码:
class A(object):
pass
print(A)
print(A.__class__)
Python 2中的执行结果如下:
<class '__main__.A'>
<type 'type'>
Python 3中的执行结果如下:
<class '__main__.A'>
<class 'type'>
可以看出,类的类型A
是type
,这意味着type
将实例化为 class,并且该 class 将被实例化为 object。
0x02 用type
动态创建类
参数的定义type
如下:
Type(name,bases,dict)\
name
:生成的类名\bases
:生成的基类列表,其类型为元组\dict
:生成的类中包含的属性或方法
假设您要创建类A
,可以使用以下方法。
cls = type('A', (object,), {'__doc__': 'class created by type'})
print(cls)
print(cls.__doc__)
输出如下:
<class '__main__.A'>
class created by type
可以看出,以这种方式创建的类与静态定义的类几乎相同,前者在使用时更加灵活。
该方法的一种使用方案是:
您可能需要将一个类作为参数传递给某些地方,并且有一些变量可以受到外部影响而在类中使用; 当然,你可以使用全局变量来解决问题,但它看起来很难看。所以在这一点上你可以使用动态创建类的方法。
这是一个例子:
import socket
try:
import SocketServer
except ImportError:
# python3
import socketserver as SocketServer
class PortForwardingRequestHandler(SocketServer.BaseRequestHandler):
'''process the request of port forwarding
'''
def handle(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(self.server) # self.server is passed in during the time of the classes being created dynamically
# connect to the target server and forward the data
# The following code is omitted...
def gen_cls(server):
'''create the subclasses dynamically
'''
return type('%s_%s' % (ProxyRequestHandler.__name__, server), (PortForwardingRequestHandler, object), {'server': server})
server = SocketServer.ThreadingTCPServer(('127.0.0.1', 8080), gen_cls(('www.tutorialdocs.com', 80)))
server.serve_forever()
在上面的例子中,由于目标服务器地址是由用户传入的,并且实现了PortForwardingRequestHandler
类的实例化ThreadingTCPServer
,我们无法控制它们。因此,使用动态创建类的方法可以很好地解决问题。
0x03 使用元类
类是实例的模板,元类是类的模板。类可以由元类创建,并且类的默认元类是type
,因此所有元类都必须是子类type
。
以下是元类的示例:
import struct
class 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):
'''a type base class
'''
__metaclass__ = MetaClass
size = 0
format = '' # strcut format
def __init__(self, val=0):
if isinstance(val, str): val = int(val)
if not isinstance(val, int):
raise TypeError('type error:%s' % type(val))
self._net_order = True # store network order data by default
self.value = val
self._num = 1
def __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('type error:%s' % type(val))
def __int__(self):
return int(self.value)
def __hex__(self):
return hex(self.value)
def __index__(self):
return self.value
def __add__(self, val):
return int(self.value + val)
def __radd__(self, val):
return int(val + self.value)
def __sub__(self, val):
return self.value - val
def __rsub__(self, val):
return val - self.value
def __mul__(self, val):
return self.value * val
def __div__(self, val):
return self.value / val
def __mod__(self, val):
return self.value % val
def __rshift__(self, val):
return self.value >> val
def __and__(self, val):
return self.value & val
@property
def net_order(self):
return self._net_order
@net_order.setter
def net_order(self, _net_order):
self._net_order = _net_order
@property
def value(self):
return self._val
@value.setter
def value(self, val):
if not isinstance(val, int):
raise TypeError('type error:%s' % type(val))
if val < 0: raise ValueError(val)
max_val = 256 ** (self.size) - 1
if val > max_val: raise ValueError('%d is more than the maximum size %d' % (val, max_val))
self._val = val
def unpack(self, buff, net_order=True):
'''extract data from buffer
'''
if len(buff) < self.size: raise ValueError(repr(buff))
buff = buff[:self.size]
fmt = self.format
if not net_order: fmt = '<' + fmt[1]
self._val = struct.unpack(fmt, buff)[0]
return self._val
def pack(self, net_order=True):
'''return the memory data
'''
fmt = self.format
if not net_order: fmt = '<' + fmt[1]
return struct.pack(fmt, self._val)
@staticmethod
def cls_from_size(size):
'''return the corresponding class from the integer size
'''
if size == 1:
return c_uint8
elif size == 2:
return c_uint16
elif size == 4:
return c_uint32
elif size == 8:
return c_uint64
else:
raise RuntimeError('Unsupported integer data length:%d' % size)
@classmethod
def unpack_from(cls, str, net_order=True):
obj = cls()
obj.unpack(str, net_order)
return int(obj)
class ArrayTypeBase(object):
'''array type base class
'''
def __init__(self, val=''):
init_val = 0
if isinstance(val, int):
init_val = val
else:
val = str(val)
self._obj_array = [self.obj_type(init_val) for _ in range(self.array_size)] # initialization
self.value = val
def __str__(self):
return str(self.value)
def __repr__(self):
return repr(self.value)
def __getitem__(self, idx):
return self._obj_array[idx].value
def __setitem__(self, idx, val):
self._obj_array[idx].value = val
def __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 result
def __add__(self, oval):
if not isinstance(oval, str):
raise NotImplementedError('%s is not supported by type %s' % (self.__class__.__name__, type(oval)))
return self.value + oval
def __radd__(self, oval):
return oval + self.value
def __iter__(self):
'''iterator
'''
for i in range(self.length):
yield self[i]
@property
def 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.setter
def value(self, val):
if isinstance(val, list):
raise NotImplementedError('ArrayType is not supported type list')
elif isinstance(val, str):
self.unpack(val)
def unpack(self, buff, net_order=True):
'''
'''
if len(buff) == 0: return
if len(buff) < self.size: raise ValueError('unpack length error:%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 result
class c_uint8(IntTypeBase):
'''unsigned char
'''
size = 1
format = '!B'
class c_ubyte(c_uint8): pass
class c_uint16(IntTypeBase):
'''unsigned short
'''
size = 2
format = '!H'
class c_ushort(c_uint16): pass
class c_uint32(IntTypeBase):
'''unsigned int32
'''
size = 4
format = '!I'
class c_ulong(c_uint32): pass
class c_uint64(IntTypeBase):
'''unsigned int64
'''
size = 8
format = '!Q'
class c_ulonglong(c_uint64): pass
cls = c_ubyte * 5
print(cls)
val = cls(65)
print(val)
Python 2.7中上述代码的输出如下:
<class '__main__.c_ubyte_Array_5'>
AAAAA
metaclass
在Python 3中修改了它的定义:
class IntTypeBase(object, metaclass=MetaClass):
pass
您可以使用基础中的方法来six
实现兼容性:
import six
@six.add_metaclass(MetaClass)
class IntTypeBase(object):
pass
使用元类的优点是您可以以更优雅的方式创建类,例如c_ubyte * 5
上面的代码,这可以提高代码的可读性。
0x04 重写new方法
从中继承的每个类object
都有一个__new__
方法,该方法将比__init__
实例化类时更早调用。它返回的类型决定了最终创建的对象的类型。
我们来看看以下代码:
class A(object):
def __new__(self, *args, **kwargs):
return B()
class B(object):
pass
a = A()
print(a)
输出如下:
<__main__.B object at 0x023576D0>
如您所见,虽然它A
在代码中实例化,但返回的对象类型是B
。这主要归功于该__new__
方法。
以下示例显示如何在以下位置动态创建类__new__
:
class B(object):
def __init__(self, var):
self._var = var
def 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()
输出如下:
Hello World
output from new class A_B
该示例实现了动态创建两个类的子类的过程,它适用于需要从排列和组合类生成许多子类的场景,这可以避免编写一堆子类代码的痛苦。
0x05 总结
您必须使用该type
实现动态创建类。但是,您可以根据场景选择不同的方法。
实际上,它对静态分析工具并不友好,因为类型在运行时已经改变。此外,它还会降低代码的可读性,因此通常不建议使用这种熟练的代码。
译文地址:https://www.tutorialdocs.com/article/python-class-dynamically.html