前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Python中动态创建类的方法

Python中动态创建类的方法

原创
作者头像
drunkdream
发布于 2018-08-02 05:56:14
发布于 2018-08-02 05:56:14
5.2K5
举报
文章被收录于专栏:醉梦轩醉梦轩

0x00 前言

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

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

0x01 类的本质

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

来看下面这段代码:

代码语言:txt
AI代码解释
复制
class A(object):
    pass

print(A)
print(A.__class__)

在Python2中执行结果如下:

代码语言:txt
AI代码解释
复制
<class '__main__.A'>
<type 'type'>

在Python3中执行结果如下:

代码语言:txt
AI代码解释
复制
<class '__main__.A'>
<class 'type'>

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

0x02 使用type动态创建类

type的参数定义如下:

type(name, bases, dict)

name: 生成的类名

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

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

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

代码语言:txt
AI代码解释
复制
cls = type('A', (object,), {'__doc__': 'class created by type'})

print(cls)
print(cls.__doc__)

输出结果如下:

代码语言:txt
AI代码解释
复制
<class '__main__.A'>
class created by type

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

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

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

以下是一个使用的示例:

代码语言:txt
AI代码解释
复制
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的子类。

下面是元类的一个例子:

代码语言:txt
AI代码解释
复制
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_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)

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

代码语言:txt
AI代码解释
复制
<class '__main__.c_ubyte_Array_5'>
AAAAA

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

代码语言:txt
AI代码解释
复制
class IntTypeBase(object, metaclass=MetaClass):
    pass

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

代码语言:txt
AI代码解释
复制
import six

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

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

0x04 重写__new__方法

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

请看以下代码:

代码语言:txt
AI代码解释
复制
class A(object):
    def __new__(self, *args, **kwargs):
        return B()

class B(object):
    pass

a = A()
print(a)

输出结果如下:

代码语言:txt
AI代码解释
复制
<__main__.B object at 0x023576D0>

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

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

代码语言:txt
AI代码解释
复制
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()

结果输出如下:

代码语言:txt
AI代码解释
复制
Hello World
output from new class A_B

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

0x05 总结

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
5 条评论
热度
最新
学习了学习了
学习了学习了
回复回复点赞举报
教程很用心!
教程很用心!
回复回复点赞举报
读过之后很有收获
读过之后很有收获
回复回复点赞举报
终于知道如何在运行时动态创建类了
终于知道如何在运行时动态创建类了
回复回复点赞举报
最近正在学习python中动态创建类的内容,这篇文章是很好的参考。
最近正在学习python中动态创建类的内容,这篇文章是很好的参考。
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
py3_cookbook_notes_02
最近在看Python Cookbook第三版,将看书过程中一些平时不太容易注意的知识点记录下来。 函数 可接受任意数量参数的函数 def avg(first, *rest): return (first + sum(rest)) / (1 + len(rest)) # Sample use avg(1, 2) # 1.5 avg(1, 2, 3, 4) # 2.5 import html def make_element(name, value, **attrs): keyvals =
jeremyxu
2018/05/11
1.3K0
3.python元类编程
1.1.propety动态属性 在面向对象编程中,我们一般把名词性的东西映射成属性,动词性的东西映射成方法。在python中他们对应的分别是属性self.xxx和类方法。但有时我们需要的属性需要根据其他属性动态的计算,此时如果直接使用属性方法处理,会导致数据不同步。下面介绍@property方法来动态创建类属性。 from datetime import datetime,date class User: def __init__(self,name,birthday): self
zhang_derek
2018/05/30
5260
Python 元类(Meta Class):解密 Python 面向对象编程的幕后推手
在 Python 编程中,我们每天都在和类打交道,但是你是否也和我一样想过:类本身是什么?是谁创建了类?元类(Meta Class)就是用来创建类的"类"。今天让我们一起深入理解这个强大而神秘的特性。
Piper破壳
2024/12/13
960
python 学习-打开潘多拉的魔盒-元类(metaclass)学习
在 Python 里面大家都比较熟悉了,通过 class 关键字创建一个类,这是通过硬编码来实现的。 那么如何动态创建一个类呢,如果给一批数据,让它动态生成一个类?
上海-悠悠
2023/08/22
2360
python 学习-打开潘多拉的魔盒-元类(metaclass)学习
Python 中类的构造方法 __new__ 的妙用
Python 的类中,所有以双下划线__包起来的方法,叫魔术方法,魔术方法在类或对象的某些事件发出后可以自动执行,让类具有神奇的魔力,比如常见的构造方法__new__、初始化方法__init__、析构方法__del__,今天来聊一聊__new__的妙用,主要分享以下几点:
somenzz
2021/10/21
5880
35个高级Python知识点总结
众所周知,Java中强调“一切皆对象”,但是Python中的面向对象比Java更加彻底,因为Python中的类(class)也是对象,函数(function)也是对象,而且Python的代码和模块也都是对象。
py3study
2020/01/08
2.3K0
Python语法-类与实例
我们会发现a被删除后,b依旧没有被销毁,这是因为对应每次被引用都会计数器+1,只有计数器为0,才会销毁。
码客说
2021/11/29
5360
原始套接字和流量嗅探
《Python黑帽子:黑客与渗透测试编程之道》的读书笔记,会包括书中源码,并自己将其中一些改写成Python3版本。书是比较老了,anyway,还是本很好的书
红客突击队
2022/09/29
1.8K0
原始套接字和流量嗅探
Python type 和元类 metaclass
众所周知 Python 跟 Java 一样,一切皆对象,所以 class 类也是对象,而对象可以动态创建,所以一个 class 类也可以被动态创建出来。
菜皮日记
2023/12/18
1570
[PYTHON] 核心编程笔记(13.P
类与实例相互关联,类是对象的定义,而实例是"真正的实物",它存放了类中所定义的对象的具体信息
py3study
2020/01/07
3900
Python 5.5 使用元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时创建的,而是运行时动态创建的。
py3study
2020/01/09
5130
python 面向对象技巧 元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
葫芦
2019/04/17
3160
Python中使用type、metacl
我们知道动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
py3study
2020/01/16
4100
python元编程
通过使用property可以将方法像属性一样获取值。使用setter对方法进行赋值操作
编程黑洞
2023/03/06
3170
类和对象的创建过程(元类,__new__,__init__,__call__)
一、 type() 1、创建类的两种方式 方式一 class MyClass(object): def func(self,name): print(name) myc = MyClass() print(MyClass, type(MyClass)) print(myc, type(myc)) 我们创建了一个名为MyClass的类,并实例化了这个类,得到其对象myc 上面代码打印的结果为: <class '__main__.MyClass'> <class 'type'>
用户1214487
2018/01/24
1.2K0
第二章、深入类和对象
2.1.鸭子类型和多态 “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”  我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。 实例一: # 鸭子类型和多态简单实例 class Dog(object): def say(self): print('a dog') class Cat(object): def say(self): print('a cat') class Duck(object):
zhang_derek
2018/05/30
9120
10-面向对象2
isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上 。
用户3106371
2019/03/11
1.5K0
在python中实现final类
当然要利用下metaclass做手脚啦 class Final(type): def __new__(cls, name, bases, classdict): for b in bases: if isinstance(b, Final): raise TypeError("type '{0}' is Final type".format(b.__name__)) return type.__new__(cls
超级大猪
2019/11/21
2.9K0
什么是元类metaclass?
在理解元类之前,需要先掌握Python中的类,Python中类的概念与SmallTalk中类的概念相似。 在大多数语言中,类是用来描述如何创建对象的代码段,这在Python中也是成立的:
用户2936342
2018/08/27
3980
Python面试题之Python面向对象编程汇总
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。
Jetpropelledsnake21
2019/02/15
1.8K0
相关推荐
py3_cookbook_notes_02
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文