0x00 前言
在Python中,类也是作为一种对象存在的,因此可以在运行时动态创建类,这也是Python灵活性的一种体现。
本文介绍了如何使用type
动态创建类,以及相关的一些使用方法与技巧。
0x01 类的本质
何为类?类是对现实生活中一类具有共同特征的事物的抽象,它描述了所创建的对象共同的属性和方法。在常见的编译型语言(如C++
)中,类在编译的时候就已经确定了,运行时是无法动态创建的。那么Python是如何做到的呢?
来看下面这段代码:
class A(object):
pass
print(A)
print(A.__class__)
在Python2中执行结果如下:
<class '__main__.A'>
<type 'type'>
在Python3中执行结果如下:
<class '__main__.A'>
<class 'type'>
可以看出,类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__)
输出结果如下:
<class '__main__.A'>
class created by type
可以看出,这样创建的类与静态定义的类基本没有什么差别,使用上还更灵活。
这种方法的使用场景之一是:
有些地方需要传入一个类作为参数,但是类中会用到某些受外界影响的变量;虽然使用全局变量可以解决这个问题,但是比较丑陋。此时,就可以使用这种方法动态创建一个类来使用。
以下是一个使用的示例:
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
的子类。
下面是元类的一个例子:
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):