元类(metaclass)

news2024/9/23 13:15:56

目录

一、引言

二、什么是元类

三、为什么用元类

四、内置函数exec(储备)

五、class创建类

5.1 type实现

六、自定义元类控制类的创建

6.1 应用

七、__call__(储备)

八、__new__(储备)

九、自定义元类控制类的实例化

一十、自定义元类后类的继承顺序

十一、练习


python从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129328397?spm=1001.2014.3001.5502

一、引言

  • 元类属于python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也只是自圆其说、点到为止,从对元类的控制上来看就破绽百出、逻辑混乱,今天我就来带大家来深度了解python元类的来龙去脉。
  • 笔者深入浅出的背后是对技术一日复一日的执念,希望可以大家可以尊重原创,为大家能因此文而解开对元类所有的疑惑而感到开心!!!

二、什么是元类

  • 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类
class Foo:  # Foo=元类()
    pass

三、为什么用元类

  • 元类是负责产生类的,所以我们学习元类或者自定义元类的目的:是为了控制类的产生过程,还可以控制对象的产生过程

四、内置函数exec(储备)

cmd = """
x=1
print('exec函数运行了')
def func(self):
    pass
"""
class_dic = {}
# 执行cmd中的代码,然后把产生的名字丢入class_dic字典中
exec(cmd, {}, class_dic)

exec函数运行了

print(class_dic)

{'x': 1, 'func': <function func at 0x10a0bc048>}

五、class创建类

  • 如果说类也是对象,那么用class关键字的去创建类的过程也是一个实例化的过程,该实例化的目的是为了得到一个类,调用的是元类
  • 用class关键字创建一个类,用的默认的元类type,因此以前说不要用type作为类别判断
class People:  # People=type(...)
    country = 'China'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print('%s is eating' % self.name)


print(type(People))

<class 'type'>

 

5.1 type实现

  • 创建类的3个要素:类名,基类,类的名称空间
  • People = type(类名,基类,类的名称空间)
class_name = 'People'  # 类名

class_bases = (object, )  # 基类

# 类的名称空间
class_dic = {}
class_body = """
country='China'
def __init__(self,name,age):
    self.name=name
    self.age=age
def eat(self):
    print('%s is eating' %self.name)
"""

exec(
    class_body,
    {},
    class_dic,
)
print(class_name)

People

print(class_bases)

(<class 'object'>,)

print(class_dic)  # 类的名称空间

{'country': 'China', '__init__': <function __init__ at 0x10a0bc048>, 'eat': <function eat at 0x10a0bcd08>}

  • People = type(类名,基类,类的名称空间)
People1 = type(class_name, class_bases, class_dic)
print(People1)

<class '__main__.People'>

obj1 = People1(1, 2)
obj1.eat()

1 is eating

  • class创建的类的调用
print(People)

<class '__main__.People'>

obj = People1(1, 2)
obj.eat()

1 is eating

六、自定义元类控制类的创建

  • 使用自定义的元类
class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __init__(self, class_name, class_bases, class_dic):
        print('self:', self)  # 现在是People
        print('class_name:', class_name)
        print('class_bases:', class_bases)
        print('class_dic:', class_dic)
        super(Mymeta, self).__init__(class_name, class_bases,
                                     class_dic)  # 重用父类type的功能
  • 分析用class自定义类的运行原理(而非元类的的运行原理):

    1. 拿到一个字符串格式的类名class_name='People'
    2. 拿到一个类的基类们class_bases=(obejct,)
    3. 执行类体代码,拿到一个类的名称空间class_dic=
    4. 调用People=type(class_name,class_bases,class_dic)
class People(object, metaclass=Mymeta):  # People=Mymeta(类名,基类们,类的名称空间)
    country = 'China'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print('%s is eating' % self.name)

self: <class '__main__.People'>
class_name: People
class_bases: (<class 'object'>,)
class_dic: {'__module__': '__main__', '__qualname__': 'People', 'country': 'China', '__init__': <function People.__init__ at 0x10a0bcbf8>, 'eat': <function People.eat at 0x10a0bc2f0>}

6.1 应用

  • 自定义元类控制类的产生过程,类的产生过程其实就是元类的调用过程
  • 我们可以控制类必须有文档,可以使用如下的方式实现
class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __init__(self, class_name, class_bases, class_dic):
        if class_dic.get('__doc__') is None or len(
                class_dic.get('__doc__').strip()) == 0:
            raise TypeError('类中必须有文档注释,并且文档注释不能为空')
        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')
        super(Mymeta, self).__init__(class_name, class_bases,
                                     class_dic)  # 重用父类的功能
try:

    class People(object, metaclass=Mymeta
                 ):  #People  = Mymeta('People',(object,),{....})
        #     """这是People类"""
        country = 'China'

        def __init__(self, name, age):
            self.name = name
            self.age = age

        def eat(self):
            print('%s is eating' % self.name)
except Exception as e:
    print(e)

类中必须有文档注释,并且文档注释不能为空

七、__call__(储备)

  • 要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法、、__call__方法,该方法会在调用对象时自动触发
class Foo:
    def __call__(self, *args, **kwargs):
        print(args)
        print(kwargs)
        print('__call__实现了,实例化对象可以加括号调用了')


obj = Foo()
obj('nick', age=18)

('nick',)
{'age': 18}
__call__实现了,实例化对象可以加括号调用了

八、__new__(储备)

我们之前说类实例化第一个调用的是__init__,但__init__其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 __new__ 方法。

__new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

注意:new() 函数只能用于从object继承的新式类。

class A:
    pass


class B(A):
    def __new__(cls):
        print("__new__方法被执行")
        return cls.__new__(cls)

    def __init__(self):
        print("__init__方法被执行")


b = B()

九、自定义元类控制类的实例化

class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(self)  # self是People
        print(args)  # args = ('nick',)
        print(kwargs)  # kwargs = {'age':18}
        # return 123
        # 1. 先造出一个People的空对象,申请内存空间
        # __new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。
        obj = self.__new__(self)  # 虽然和下面同样是People,但是People没有,找到的__new__是父类的
        # 2. 为该对空对象初始化独有的属性
        self.__init__(obj, *args, **kwargs)
        # 3. 返回一个初始化好的对象
        return obj
  • People = Mymeta(),People()则会触发__call__
class People(object, metaclass=Mymeta):
    country = 'China'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print('%s is eating' % self.name)


#     在调用Mymeta的__call__的时候,首先会找自己(如下函数)的,自己的没有才会找父类的
#     def __new__(cls, *args, **kwargs):
#         # print(cls)  # cls是People
#         # cls.__new__(cls) # 错误,无限死循环,自己找自己的,会无限递归
#         obj = super(People, cls).__new__(cls)  # 使用父类的,则是去父类中找__new__
#         return obj
  • 类的调用,即类实例化就是元类的调用过程,可以通过元类Mymeta的__call__方法控制

  • 分析:调用Pepole的目的

    1. 先造出一个People的空对象
    2. 为该对空对象初始化独有的属性
    3. 返回一个初始化好的对象
obj = People('nick', age=18)

<class '__main__.People'>
('nick',)
{'age': 18}

print(obj.__dict__)

{'name': 'nick', 'age': 18}

一十、自定义元类后类的继承顺序

结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???

在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object

class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    n = 444

    def __call__(self, *args,
                 **kwargs):  #self=<class '__main__.OldboyTeacher'>
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj


class Bar(object):
    n = 333


class Foo(Bar):
    n = 222


class OldboyTeacher(Foo, metaclass=Mymeta):
    n = 111

    school = 'oldboy'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)


print(
    OldboyTeacher.n
)  # 自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type

111

print(OldboyTeacher.n)

111

  • 查找顺序:

    1. 先对象层:OldoyTeacher->Foo->Bar->object
    2. 然后元类层:Mymeta->type

依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__的查找

class Mymeta(type):
    n = 444

    def __call__(self, *args,
                 **kwargs):  #self=<class '__main__.OldboyTeacher'>
        obj = self.__new__(self)
        print(self.__new__ is object.__new__)  #True


class Bar(object):
    n = 333

    # def __new__(cls, *args, **kwargs):
    #     print('Bar.__new__')


class Foo(Bar):
    n = 222

    # def __new__(cls, *args, **kwargs):
    #     print('Foo.__new__')


class OldboyTeacher(Foo, metaclass=Mymeta):
    n = 111

    school = 'oldboy'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)

    # def __new__(cls, *args, **kwargs):
    #     print('OldboyTeacher.__new__')


OldboyTeacher('nick',
              18)  # 触发OldboyTeacher的类中的__call__方法的执行,进而执行self.__new__开始查找

总结,Mymeta下的__call__里的self.__new__在OldboyTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__

十一、练习

需求:使用元类修改属性为隐藏属性

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        # 加上逻辑,控制类Foo的创建
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):
        # 加上逻辑,控制Foo的调用过程,即Foo对象的产生过程
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        # 修改属性为隐藏属性
        obj.__dict__ = {
            '_%s__%s' % (self.__name__, k): v
            for k, v in obj.__dict__.items()
        }

        return obj
class Foo(object, metaclass=Mymeta):  # Foo = Mymeta(...)
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex


obj = Foo('nick', 18, 'male')
print(obj.__dict__)

{'_Foo__name': 'egon', '_Foo__age': 18, '_Foo__sex': 'male'}

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

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

相关文章

mysql my.ini、登录、用户相关操作、密码管理、权限管理、权限表,角色管理

my.ini 配置文件格式 登录mysql mysql -h hostname | IP -P port -u username -p database -e “select 语句”&#xff1b; 创建用户、修改用户、删除用户 create user ‘zen’ identified by ‘密码’ ## host 默认是 % create user ‘zen’‘localhost’ identified by ‘密…

构建安全可信、稳定可靠的RISC-V安全体系

安全之安全(security)博客目录导读 2023 RISC-V中国峰会 安全相关议题汇总 说明&#xff1a;本文参考RISC-V 2023中国峰会如下议题&#xff0c;版权归原作者所有。

YARN资源管理框架论述

一、简介 为了实现一个Hadoop集群的集群共享、可伸缩性和可靠性&#xff0c;并消除早期MapReduce框架中的JobTracker性能瓶颈&#xff0c;开源社区引入了统一的资源管理框架YARN。 YARN是将JobTracker的两个主要功能&#xff08;资源管理和作业调度/监控&#xff09;分离&…

HarmonyOS/OpenHarmony(Stage模型)卡片开发应用上下文Context使用场景一

1.获取应用文件路径 基类Context提供了获取应用文件路径的能力&#xff0c;ApplicationContext、AbilityStageContext、UIAbilityContext和ExtensionContext均继承该能力。应用文件路径属于应用沙箱路径。上述各类Context获取的应用文件路径有所不同。 通过ApplicationContext…

VR法治警示教育:情景式课堂增强教育效果

VR法治警示教育平台是一款基于虚拟现实技术的在线教育平台&#xff0c;旨在通过模拟真实场景和互动体验&#xff0c;向公众普及法律知识&#xff0c;提高公民的法律意识和素养。该平台采用先进的虚拟现实技术&#xff0c;将用户带入一个逼真的仿真环境&#xff0c;让用户身临其…

【广州华锐互动】VR沉浸式体验红军长征路:追寻红色记忆,传承红色精神

在历史的长河中&#xff0c;长征无疑是一段充满艰辛和英勇的伟大征程。为了让更多的人了解这段历史&#xff0c;我们利用虚拟现实&#xff08;VR&#xff09;技术&#xff0c;为您带来一场沉浸式的体验&#xff0c;重温红军万里长征的壮丽篇章。 一、踏上长征之路 戴上VR眼镜&a…

RocketMQ教程-(6-5)-运维部署-Promethus Exporter

介绍​ Rocketmq-exporter 是用于监控 RocketMQ broker 端和客户端所有相关指标的系统&#xff0c;通过 mqAdmin 从 broker 端获取指标值后封装成 87 个 cache。 警告 过去版本曾是 87 个 concurrentHashMap&#xff0c;由于 Map 不会删除过期指标&#xff0c;所以一旦有 la…

南方CASS软件安装包分享

目录 一、软件简介 二、软件下载 一、软件简介 南方CASS软件是一款基于AutoCAD平台开发的测量和计算设计软件&#xff0c;广泛应用于水利、电力、市政、建筑、交通等领域。 南方CASS软件集成了地形测量、断面测量、土地勘测定界、水文水利和公路设计等功能&#xff0c;为测…

JS 数组中对象 某属性相同对某属性的值进行相加去重(支持多条件多个值判断相加)

/* delSameObjValue 数组对象相同值相加去重arr 需要处理的数组resultNum 最终计算结果的键名keyName 用于计算判断的键名 keyValue 用于计算结果的键名 --> 对应的键值为number类型 */function delSameObjValue(arr, resultNum, keyName, keyValue) {const warp new Map(…

微信开发之一键创建微信群聊的技术实现

创建微信群 本接口为敏感接口&#xff0c;请查阅调用规范手册创建后&#xff0c;手机上不会显示该群&#xff0c;往该群主动发条消息手机即可显示。 请求URL&#xff1a; http://域名地址/createChatroom 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-T…

Navisworks2020~2023安装包分享

目录 一、软件介绍 二、下载地址 一、软件介绍 Navisworks是一款专业的建筑、工厂、机械和设备设计软件工具&#xff0c;旨在帮助项目相关方可靠地整合、分享和审阅详细的三维设计模型。它提供了一系列功能强大的工具&#xff0c;使设计师、工程师和建筑师能够更好地协作、沟…

PB4引脚作GPIO上电高电平问题

问题说明 给旧项目debug&#xff0c;芯片是国民技术 N32G452VEL7 &#xff08;用起来跟32没多大差 包括PB4在内有多个引脚作为输出&#xff0c;默认低电平&#xff0c;在状态机内先输出高电平再回到低电平&#xff0c;来模拟按键的状态&#xff08;相当于按键按下松开后按键功…

Docker使用mysql:5.6和 owncloud 镜像,构建一个个人网盘,安装搭建私有仓库 Harbor

一、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 [rootlocalhost ~]# docker pull mysql:5.6[rootlocalhost ~]# docker pull owncloud[rootlocalhost ~]# docker run -itd --name mysql --env MYSQL_ROOT_PASSWORD123456 mysql:5.6 d45cc5b95f00692881baaf…

vscode的远程代码调试

目录 ssh连接 xdebug调试 ssh连接 在vscode中下载该插件 这里用虚拟机测试&#xff0c;这里用虚拟机测试&#xff0c;注意ssh是可以连接的 然后安装好remote后&#xff0c;点击左下角的>< 在弹出的这个上选择connect to host连接一台主机 配置完用户名和IP后再点一次发…

<C++>泛型编程-模板

1.泛型编程 如何实现一个通用的交换函数呢&#xff1f;可以使用函数重载 void Swap(int &left, int &right) {int temp left;left right;right temp; }void Swap(double &left, double &right) {double temp left;left right;right temp; }void Swap(c…

ES6中promise的使用

ES6中promise的使用 本文目录 ES6中promise的使用基础介绍箭头函数function函数状态 原型方法Promise.prototype.then()Promise.prototype.catch() 静态方法Promise.all()Promise.race()Promise.any() 链式回调 基础介绍 官网&#xff1a;https://promisesaplus.com/ window.…

数据处理 | Python实现基于DFCP张量分解结合贝叶斯优化的缺失数据填补

数据处理 | Python实现基于DFCP张量分解结合贝叶斯优化的缺失数据填补 目录 数据处理 | Python实现基于DFCP张量分解结合贝叶斯优化的缺失数据填补实践过程基本介绍研究背景程序设计参考资料实践过程 基本介绍 数据处理 | Python实现基于DFCP张量分解结合贝叶斯优化的缺失数据填…

bug复刻,解决方案---在改变div层级关系时,导致传参失败

问题描述&#xff1a; 在优化页面时&#xff0c;为了实现网页顶部遮挡效果&#xff08;内容滚动&#xff0c;顶部导航栏不随着一起滚动&#xff0c;并且覆盖&#xff09;&#xff0c;做法是将内容都放在一个div里面&#xff0c;为这个新的div设置样式&#xff0c;margin-top w…

Android 之 LayoutInflater (布局服务)

本节引言&#xff1a; 本节继续带来的是Android系统服务中的LayoutInflater(布局服务)&#xff0c;说到布局&#xff0c;大家第一时间 可能想起的是写完一个布局的xml&#xff0c;然后调用Activity的setContentView()加载布局&#xff0c;然后把他显示 到屏幕上是吧~其实这个底…

lnmp架构-tomcat session

tomcat session共享 部署 tomcat 在server2 和 server3上进行同样的操作 保存的信息都会在tomcat日志中保存 tomcat 与nginx的整合 在server1上 当并发量开始增大时 就得做负载均衡 解决 当一个tomcat服务器挂掉后 另一个服务器上有第一个服务器上提交的数据问题 在server2…