python 元类

news2024/11/26 14:54:06

目录

  • 元类type介绍
  • 使用元类创建类
    • 直接使用type
    • 继承type
  • 类和对象的创建过程
  • 元类有什么用
  • 使用元类实现一个单例模式

元类type介绍

我们知道在python中一切皆对象,即使是类,也是对象,那么类这个对象是的类是谁呢?那就是元类

通过 type()obj.__class__ 可查看对象的元类:

# 元类
class A:
    pass


if __name__ == "__main__":
    a = A()
    # 1.查看自定义对象的元类
    print(type(a))  # <class '__main__.A'>
    # 2.查看类A的元类
    print(type(A))  # <class 'type'>
    # 3.python一些内置类型的元类
    print(type(str))  # <class 'type'>
    print(type(int))  # <class 'type'>
    print(type(float))  # <class 'type'>
    # 4.甚至type类的元类还是自己
    print(type(type))  # <class 'type'>

从上面结果可以看出:

  1. 普通对象的元类可直接查看是创建对象的类
  2. 自己创建的一些类的元类是type
  3. python内置的一些基本类型的元类是type
  4. type自己的元类也是type

所以可以说:所有的类终归都是由 type 实例化得来的,它就是最元始的,同时它自己也是一个类,所以也可以称之为元类(Metaclass)。

实例、类、元类之间的关系如图所示:

在这里插入图片描述

使用元类创建类

既然前面说了所有的类终究都是由元类创建的,那么下面看看,除了我们经常使用class关键字来快速创建类,如何使用元类来创建类。

通常有以下两种方法

直接使用type

type源码如下

class type(object):
    """
    type(object_or_name, bases, dict)
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    """
    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass

创建type对象一般有3个参数:

  • name:新类的名称;
  • bases:以元组的形式,声明父类;
  • dict:以字典的形式声明类的属性;

示例如下:

# 元类

if __name__ == "__main__":
    # 通过type创建类A
    A = type("A", (), {})
    # 创建类A的实例a
    a = A()

    print(type(a))  # <class '__main__.A'>
    print(type(A))  # <class 'type'>
    print(A.__bases__)  # (<class 'object'>,)

从结果可以看出我们使用type成功创建一个类A。

虽然没有给 bases 赋值,但是 Python 中所有的类都是以 object 为基类,所以查看类 Foo1 的父类,会得到这样一个结果。PS:__bases__查看父类。

通过type创建带属性的类:
注意属性是类属性

if __name__ == "__main__":
    User = type("User", (), {"name":"user"})
    my_obj = User()
    print(my_obj)
    # my_obj实例有属性
    print(my_obj.name)

通过type创建带方法的类

def say(self):
    return "i am user"
    # return self.name

if __name__ == "__main__":
    User = type("User", (), {"name":"user", "say":say})
    my_obj = User()
    print(my_obj)
    print(my_obj.say())

通过type创建带父类的类

def say(self):
    return "i am user"
    # return self.name

class BaseClass:
    def answer(self):
        return "i am baseclass"

if __name__ == "__main__":
    User = type("User", (BaseClass, ), {"name":"user", "say":say})
    my_obj = User()
    print(my_obj)
    print(my_obj.answer())

继承type

如果一个类继承了type类,那么这个类也是元类,使用metaclass可以指定某个类的元类是哪个。来这种方式使用的较多。

下面是一个简单的示例:

class MetaClass(type):
    pass


class User(metaclass=MetaClass):
    pass


if __name__ == "__main__":
    my_obj = User()
    print(my_obj)

MetaClass 继承了 type ,就可以称为元类,而 User 类与之前定义的类的不同之处就在于,类名后括号内添加了参数 metaclass 来指定元类(即指定使用哪个元类来创建当前类,默认是type),这样就利用了元类 MetaClass 创建了类 User

类和对象的创建过程

上面知道了如何去定义一个元类,下面看看,元类是如何创建一个类的。

看一个下面的例子:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("new of MetaClass")
        return super(MetaClass, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print("init of MetaClass")
        super(MetaClass, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("call of MetaClass")
        super(MetaClass, self).__call__( *args, **kwargs)

class User(metaclass=MetaClass):
    pass

直接运行文件:打印如下:

new of MetaClass
init of MetaClass

可以看到与对象的实例化过程很像。

当使用class关键字定义一个类时,其实是先去寻找这个类的元类,找到元类后,先调用元类的__new__方法实例化一个类对象,然后调用__init__去初始化这个类对象。

另外:
创建类时,会先检查当前类是否有元类,没有再找父类是否有,没有再去模块中找,再没有再使用type创建类。

然后我使用User类来创建一个对象:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("new of MetaClass")
        return super(MetaClass, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print("init of MetaClass")
        super(MetaClass, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("call of MetaClass")
        super(MetaClass, self).__call__(self, *args, **kwargs)


class User(metaclass=MetaClass):
    pass


if __name__ == "__main__":
    print("--- 开始创建User对象")
    my_obj = User()

运行打印结果如下:

new of MetaClass
init of MetaClass
--- 开始创建User对象
call of MetaClass

在使用元类的__new__方法和__init__去创建完类后,实例化这个类时,会调用元类的__call__方法,这个方法会返回实例化后的对象。

其实在实例化对象时,这个元类的__call__方法会自己调类的__new__方法和__init__去创建并初始化对象,然后返回。

看下面的示例:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("new of MetaClass")
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print("init of MetaClass")
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("call of MetaClass")
        return super().__call__(*args, **kwargs)


class User(metaclass=MetaClass):
    def __new__(cls, *args, **kwargs):
        print("new in User")
        return super().__new__(cls)

    def __init__(self, name, age):
        print("init in User")
        self.name = name
        self.age = age



if __name__ == "__main__":
    print("--- 开始创建User对象")
    my_obj = User("a", 10)
    print(my_obj)

打印结果如下:

new of MetaClass
init of MetaClass
--- 开始创建User对象
call of MetaClass
new in User
init in User
<__main__.User object at 0x7f9b58056d30>

可以很清楚的看到从创建类到类的实例化的过程,可总结如下:

  1. 创建类:调用原元类的__new__方法和__init__方法创建了一个类;
  2. 创建对象:调用了原类的__call__方法;
  3. 创建对象:__call__方法调类的__new__方法和__init__去创建并初始化对象,然后返回。

元类有什么用

一般我们不会自己定义元类,但是会再一些框架只能怪经常见到。

其主要作用就是在定义一些类时有一些限制或特殊要求。

比如我想限制一个测试类必须以Test开头:

class TestType(type):
    def __new__(cls, name, bases, attrs):
        if not name.startswith("Test"):
            raise AttributeError("类名请以Test开头")
        return super().__new__(cls,  name, bases, attrs)


class TestUser(metaclass=TestType):
    pass


if __name__ == "__main__":
    print("--- 开始创建对象")
    test_obj = TestUser()

如果我定义了类不以Test开头就会报错

class TestType(type):
    def __new__(cls, name, bases, attrs):
        if not name.startswith("Test"):
            raise AttributeError("类名请以Test开头")
        return super().__new__(cls,  name, bases, attrs)


class User(metaclass=TestType):
    pass


if __name__ == "__main__":
    print("--- 开始创建对象")
    test_obj = User()
	# AttributeError: 类名请以Test开头

使用元类实现一个单例模式

所谓的单例模式,就是指一个类不论我实例化多少次,都指返回同一个对象。主要用在一些公用资源、文件对象等。比如我们的数据库连接池。

示例如下:

class Meta(type):
    __instance = None

    def __call__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = type.__call__(cls, *args, **kwargs)
        return cls.__instance


class DbPool(metaclass=Meta):
    pass


if __name__ == "__main__":
    x = DbPool()
    y = DbPool()
    print(x)
    print(y)
    print(x==y)
    
    # <__main__.DbPool object at 0x7fc210182e20>
    # <__main__.DbPool object at 0x7fc210182e20>
    # True

上面主要是让我们的DbPool类使用我们自定义的元类Meta。然后重写了Meta例的__call__方法。为什么呢?回一下前面讲的,在实例化对象时,会调用元类的__call__方法返回实例化后的对象,所以为了返回同一个对象,可以在__call__方法里返回同一个即可。

参考:
https://www.cnblogs.com/XiaoYang-sir/p/16524775.html
https://blog.csdn.net/weixin_43988680/article/details/123903473

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Autosar NM网络管理机制(三大三小模式切换、NM网络报文的帧格式解析、NM中用到的定时器配置)

网络管理机制 AUTOSAR网络管理机制其实十分简单&#xff0c;概括下来三句话&#xff1a; 需要通信就发网络报文&#xff0c;否则就停发网络报文&#xff0c;所有节点同睡同醒。 下图是网络管理的状态机&#xff1a; 网络管理包括三个模式&#xff1a;Network Mode、Prepare …

【指针初阶 2023516】

#include <stdio.h>int main() {int a 100;int * pa &a;//pa是专门用来存放地址&#xff08;指针&#xff09;的&#xff0c;这里的pa就被称为指针变量char* pc &a;//指针变量在32为平台下是4个字节//指针变量在64为平台下是8个字节//int arr[10];//printf(&q…

计算机图形学-GAMES101-3

一、transformation-变换 &#xff08;1&#xff09;Scale 缩放变换示意图 图形每个像素对应坐标的计算公式 写成缩放矩阵的形式 非均匀缩放 &#xff08;2&#xff09;Reflection Matrix 镜面变换示意图 图形每个像素对应坐标的计算公式 写成镜像矩阵的形式 …

阿里HR:年轻人你把握不住.....

前言 去阿里面试测试工程师&#xff0c;这里面水太深&#xff0c;什么未来规划&#xff0c;职业发展的东西都是虚拟的&#xff0c;作者还太年轻&#xff0c;没有那个经历&#xff0c;把握不住。项目只有几个&#xff0c;开心快乐就行&#xff0c;不PK&#xff0c;文明PK。 很…

文心一言 VS chatgpt (17)-- 算法导论4.1 3~4题

三、在你的计算机上实现最大子数组问题的暴力算法和递归算法。请指出多大的问题规模n0是性能交叉点一一从此之后递归算法将击败暴力算法?然后&#xff0c;修改递归算法的基本情况一一当问题规模小于 n0 时采用暴力算法。修改后&#xff0c;性能交叉点会改变吗? 文心一言&…

进程间通信(匿名管道、命名管道、共享内存)

目录 匿名管道 创建管道---pipe() 命名管道 创建FIFO FIFO 操作 用命名管道实现server&client通信 共享内存 1.创建共享内存函数shmget() 2.获得共享内存地址函数shmat() 3.删除共享内存函数shmdt() 4.共享内存控制函数shmctl() 在Linux下的多个进程间的通信机制…

Unittest单元测试框架之unittest的第三方库paramunittest参数化基础及应用

一、unittest参数化介绍 参数化测试用于当需要使用多组不同的测试数据测试同一个方法的时候 paramunittest是unittest实现参数化的一个专门的模块&#xff0c;可以传入多组参数&#xff0c;自动生成多个用例 实现unittest参数化的方式有多种&#xff0c;如ddt、paramunittest等…

Linux-进程信号

Linux-进程信号 一&#xff0c;信号入门信号的概念生活中的信号技术应用角度的信号使用kill -l 查看信号列表信号的处理方式 二&#xff0c;信号产生通过终端按键产生信号Core Dumpcode dump标志位 通过系统调用向进程发信号killraiseabort 由软件条件产生信号SIGPIPESIGALRM 硬…

三届跻身世界级,长沙国际工程机械展的突破之路

文 | 智能相对论 作者 | 易不二 长沙正在成为全球工程机械企业争相奔赴的产业“盛宴”。 近日完美落幕的第三届长沙国际工程机械展&#xff0c;留下了全球30多个国家、1500余家企业“同台竞技”的精彩亮相&#xff0c;并达成了536亿元的现场交易额。 卡特彼勒、日立、沃尔沃…

2022年蓝桥杯:第十三届蓝桥杯大赛软件赛省赛C/C++大学B组真题(考后回顾,文末附真题链接)

目录 第十三届蓝桥杯大赛软件赛省赛C/C大学B组真题&#xff08;考后回顾&#xff09;试题 A: 九进制转十进制试题 B: 顺子日期试题 C: 刷题统计试题 D: 修剪灌木试题 E: X 进制减法试题 F: 统计子矩阵试题 G: 积木画试题 H: 扫雷试题 I: 李白打酒加强版试题 J: 砍竹子 第十三届…

【JavaStript】

目录 &#x1f437;1. JavaScript 的书写形式 &#x1f6e9;1.1 行内式 &#x1f49b; 1.2 内嵌式 &#x1f357;1.3 外部式 &#x1f37f;2. JavaScricpt 的一些常用语句 &#x1f6f4;2.1 输入&#xff1a;prompt &#x1f47d;2.2 输出&#xff1a;alert &#x1f…

Linux线程间的同步和互斥 进程间传统通信方式 5.16

Linux线程间的同步和互斥 同步&#xff1a;有顺序的考虑 按照约定的顺序相互配合完成一件事情&#xff08;红绿灯&#xff09; {释放 产生 资源1&#xff08;V操作&#xff09;&#xff1b;&#xff1b;申请 资源-1&#xff08;p操作&#xff09;} 信号量代表某一类资源&am…

RocketMQ整理

RocketMQ在阿里云上的商业版本,集成了阿里内部一些更深层次的功能及运维定制。开源版本,功能上略有缺失,但大体上是一样的。 使用Java开发,便于深度定制。最早叫MetaQ。消息吞吐量虽然依然不如Kafka,但是却比RabbitMQ高很多。在阿里内部,RocketMQ集群每天处理的请求数超过…

监控需求来源及主流方案对比

我们从开始了解监控系统来说&#xff0c;首先我们要先了解监控的需求来源&#xff0c;即监控系统都可以用于做什么? 监控需求来源 其实最初的需求很简单&#xff0c;即"系统出问题了我们要能及时感知"。后面随着技术的不断发展&#xff0c;我们对监控系统提出了更…

Linux的超级用户及权限

目录 一:Linux下的两个用户 二&#xff1a;权限 1&#xff1a;目录文件 文件创建的默认权限 2&#xff1a;普通文件 一:Linux下的两个用户 在使用Linux的时候会有两个身份,第一个是普通用户,普通用户在很多方面是受阻的,原因就是权限不够,在这种情况下就有一个超级用户,也…

iOS图片系列一 图片的基本属性

图片在项目的开发中使用频率很高&#xff0c;但是绝大部分都是作为普通的展示或者偶尔需要裁剪&#xff0c;并不需要对图片做什么特别的处理&#xff0c;最近做了一个项目对于图片的需求功能比较多&#xff0c;踩了很多坑的同时也对图片的使用有了更深的理解&#xff0c;整理下…

C++面经:初始化全局变量和未初始化全局变量有什么区别

全局变量初始化而且初始值不为0&#xff0c;那么这样的全局变量是放在内存的.data段的&#xff0c;如果全局变量初始值为0或者未初始化&#xff0c;那么这样的全局变量是放在.bss段的。 考点&#xff1a; 考察C/C语言内存模型&#xff0c;.data&#xff0c;.bss段存放的内容。 …

Windows shell环境: 从git bash切换到msys2

文章目录 1. 目的2. msys2 环境 (Environment)3. 升级 MSYS2: 使用 pacman 滚动式升级整个系统4. 在 Windows Terminal 中增加显示 MSYS25. 使用 zsh6. VSCode 中的配置增加 MSYS2 终端配置 git 路径 7. 安装 C/C 依赖库安装 ag查询 bison 和 flex 的安装目录 8. References 1.…

ES6模块化规范

在没有ES6模块化规范前&#xff0c;有像AMD、CMD这样的浏览器模块化规范&#xff0c;还有像CommonJS这样的服务端模块化规范。 2015年&#xff0c;JS终于推出了官方的模块化规范&#xff0c;为了统一各种规范&#xff0c;我们简称ES6 模块化。 ES6目前作为JS的内置模块化系统&a…

Spring的创建和使用,存储和读取Bean总结

目录 Spring项目创建和使用流程 1.创建一个 Spring 项目 2.存储 Bean 3.读取 Bean ApplicationContext和BeanFactory的区别 通过注解存储 Bean对象 五大类注解的关系 Java程序标准分层 方法注解Bean 注入Bean对象的三种方式 1.属性注入 2.Setter注入 3.构造方法注入…