Python中动态创建类的方法

0x00 前言

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

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

0x01 类的本质

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

来看下面这段代码:

1
2
3
4
5
6
class A(object):
pass

print(A)
print(A.__class__)

在Python2中执行结果如下:

1
2
<class '__main__.A'>
<type 'type'>

在Python3中执行结果如下:

1
2
<class '__main__.A'>
<class 'type'>

可以看出,类A的类型是type,也就是说:type实例化后是实例化后是对象

0x02 使用type动态创建类

type的参数定义如下:

type(name, bases, dict)

name: 生成的类名
bases: 生成的类基类列表,类型为tuple
dict: 生成的类中包含的属性或方法

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

1
2
3
4
cls = type('A', (object,), {'__doc__': 'class created by type'})

print(cls)
print(cls.__doc__)

输出结果如下:

1
2
<class '__main__.A'>
class created by type

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

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

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

以下是一个使用的示例:

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

import socket
try:
import SocketServer
except ImportError:
# python3
import socketserver as SocketServer

class 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()

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

0x03 使用元类(metaclass

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

下面是元类的一个例子:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
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):
'''类型基类
'''
__metaclass__ = MetaClass
size = 0
format = '' # 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 = 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('类型错误:%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('类型错误:%s' % type(val))
if val < 0: raise ValueError(val)
max_val = 256 ** (self.size) - 1
if val > max_val: raise ValueError('%d超过最大大小%d' % (val, max_val))
self._val = val

def unpack(self, buff, net_order=True):
'''从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):
'''返回内存数据
'''
fmt = self.format
if not net_order: fmt = '<' + fmt[1]
return struct.pack(fmt, self._val)

@staticmethod
def cls_from_size(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('不支持的整型数据长度:%d' % size)

@classmethod
def 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 = 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)] # 初始化
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还不支持%s类型' % (self.__class__.__name__, type(oval)))
return self.value + oval

def __radd__(self, oval):
return oval + self.value

def __iter__(self):
'''迭代器
'''
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还不支持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数据长度错误:%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_uint16(IntTypeBase):
'''unsigned short
'''
size = 2
format = '!H'

class c_uint32(IntTypeBase):
'''unsigned int32
'''
size = 4
format = '!I'

class c_uint64(IntTypeBase):
'''unsigned int64
'''
size = 8
format = '!Q'


cls = c_ubyte * 5
print(cls)
val = cls(65)
print(val)

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

1
2
3
4

<class '__main__.c_ubyte_Array_5'>
AAAAA

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

1
2
3
4

class IntTypeBase(object, metaclass=MetaClass):
pass

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

1
2
3
4
5
6
7

import six

@six.add_metaclass(MetaClass)
class IntTypeBase(object):
pass

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

0x04 重写__new__方法

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

请看以下代码:

1
2
3
4
5
6
7
8
9
10
11

class A(object):
def __new__(self, *args, **kwargs):
return B()

class B(object):
pass

a = A()
print(a)

输出结果如下:

1
<__main__.B object at 0x023576D0>

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

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()

结果输出如下:

1
2
Hello World
output from new class A_B

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

0x05 总结

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

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