元类(metaclass)

目录

  • 一、引言
  • 二、什么是元类
  • 三、为什么用元类
  • 四、内置函数exec(储备)
  • 五、class创建类
    • 5.1 type实现
  • 六、自定义元类控制类的创建
    • 6.1 应用
  • 七、__call__(储备)
  • 八、__new__(储备)
  • 九、自定义元类控制类的实例化
  • 一十、自定义元类后类的继承顺序
  • 十一、练习

一、引言

  • 元类属于python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也只是自圆其说、点到为止,从对元类的控制上来看就破绽百出、逻辑混乱,今天我就来带大家来深度了解python元类的来龙去脉。

  • 笔者深入浅出的背后是对技术一日复一日的执念,希望可以大家可以尊重原创,为大家能因此文而解开对元类所有的疑惑而感到开心!!!

二、什么是元类

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

114-元类metaclass-类的创建.png?x-oss-process=style/watermark

三、为什么用元类

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

四、内置函数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 = nameself.age = agedef eat(self):print('%s is eating' % self.name)
print(type(People))
<class 'type'>

114-元类metaclass-class关键字.png?x-oss-process=style/watermark

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=nameself.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)  # 现在是Peopleprint('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 = nameself.age = agedef 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 = nameself.age = agedef 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:passclass 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是Peopleprint(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 = nameself.age = agedef 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 = 444def __call__(self, *args,**kwargs):  #self=<class '__main__.OldboyTeacher'>obj = self.__new__(self)self.__init__(obj, *args, **kwargs)return objclass Bar(object):n = 333class Foo(Bar):n = 222class OldboyTeacher(Foo, metaclass=Mymeta):n = 111school = 'oldboy'def __init__(self, name, age):self.name = nameself.age = agedef 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 = 444def __call__(self, *args,**kwargs):  #self=<class '__main__.OldboyTeacher'>obj = self.__new__(self)print(self.__new__ is object.__new__)  #Trueclass 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 = 111school = 'oldboy'def __init__(self, name, age):self.name = nameself.age = agedef 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): vfor k, v in obj.__dict__.items()}return obj
class Foo(object, metaclass=Mymeta):  # Foo = Mymeta(...)def __init__(self, name, age, sex):self.name = nameself.age = ageself.sex = sexobj = Foo('nick', 18, 'male')
print(obj.__dict__)
{'_Foo__name': 'egon', '_Foo__age': 18, '_Foo__sex': 'male'}

转载于:https://www.cnblogs.com/nickchen121/p/10992975.html

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

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

相关文章

Linux环境下使用rpm包安装GitLab

1.安装依赖环境 [rootgitlab ~]# yum install curl openssh-server postfix cronie 2.下载安装GitLab包 我安装的环境是Red Hat Enterprise Linux Server release 7.4 (Maipo) GitLab下载地址:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7 以上是清华大学开源…

面试字节跳动Android工程师该怎么准备?深度解析,值得收藏

前言 Android高级架构师需要学习哪些知识呢&#xff1f; 下面总结一下我认为作为一个资深开发者需要掌握的技能点。 1.Android开发的几个阶段 我的10年开发生涯中&#xff0c;有9年都是做Android相关开发&#xff0c;以我个人的经历来看&#xff0c;Android开发市场分为以下…

阿里巴巴Android面试都问些什么?系列篇

Google 为了帮助 Android 开发者更快更好地开发 App&#xff0c;推出了一系列组件&#xff0c;这些组件被打包成了一个整体&#xff0c;称作 Android Jetpack&#xff0c;它包含的组件如下图所示&#xff1a; 老的 support 包被整合进了 Jetpack&#xff0c;例如上图 Foundatio…

docker-compose安装elk7.1.1版本

在用docker-compose编排elk三个服务时&#xff0c;碰到了很多坑&#xff0c;网上很多资料编排的版本都不是最新的版本&#xff0c;我们这里用的 elasticsearch&#xff0c;logstash&#xff0c;kibana全都是elastic官方提供的目前最新版本7.1.1&#xff0c;高版本和低版本的一些…

阿里P8成长路线!我的头条面试经历分享,吊打面试官系列!

正式加入字节跳动&#xff0c;分享一点面试小经验 今天正式入职了字节跳动。工号超吉利&#xff0c;尾数是3个6。然后办公环境也很好&#xff0c;这边一栋楼都是办公区域。公司内部配备各种小零食、饮料&#xff0c;还有免费的咖啡。15楼还有健身房。而且公司包三餐来着。下午…

Docker安装部署ELK教程 (Elasticsearch+Kibana+Logstash)

Elasticsearch 是个开源分布式搜索引擎&#xff0c;它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自动发现&#xff0c;索引自动分片&#xff0c;索引副本机制&#xff0c;restful风格接口&#xff0c;多数据源&#xff0c;自动搜索负载等。 Logstash 是一个完…

阿里P8面试官都说太详细了,面试资料分享

背景 知乎客户端中有一个自己维护的 Hybrid 框架&#xff0c;在此基础上开发了一些 Hybrid 页面&#xff0c;当需要前端或者客户端开发接口的时候&#xff0c;就涉及到联调的问题。 和一般的 前端 <> 服务端&#xff0c;或者 客户端 <> 服务端 类似&#xff0c;前…

virtual和override

偶然间看到的题&#xff0c;借此记录。 class Program{static void Main(string[] args){D d new D(); //第一个D是申明类&#xff0c;第二个D是实例类A a d;B b d;C c d;a.F();b.F();c.F();d.F();}class A{public virtual void F() { Console.WriteLine("A.F")…

阿里内部资料!如何试出一个Android开发者真正的水平?系列教学

前言 马爸爸总结了一句话&#xff1a;跳槽&#xff0c;要么是钱不到位&#xff0c;要么是受了委屈。 我给自己这次的跳槽经历做了一个分析&#xff0c;希望能对那些想换工作的朋友有所帮助。 许多朋友想换工作&#xff0c;但是对“换工作”的理解可能仅限于写简历、投简历、…

CentOS7 linux下yum安装redis以及使用

CentOS7 linux下yum安装redis以及使用 1.安装redis数据库 1 yum install redis 2.下载fedora的epel仓库 yum install epel-release 3.启动redis服务 systemctl start redis 4.查看redis状态 systemctl status redis systemctl stop redis 停止服务 systemctl restart r…

Codeforces 1182A Filling Shapes

题目链接&#xff1a;http://codeforces.com/problemset/problem/1182/A 思路&#xff1a;n为奇数时不可能完全填充&#xff0c;ans 0。发现若要完全填充&#xff0c;每俩列可产生俩种情况&#xff0c;所以为 ans 2n/2 AC代码&#xff1a; 1 #include<bits/stdc.h>2 us…

阿里大神最佳总结Flutter进阶学习笔记,技术详细介绍

开头 很多人工作了十年&#xff0c;但只是用一年的工作经验做了十年而已。 高级工程师一直是市场所需要的&#xff0c;然而很多初级工程师在进阶高级工程师的过程中一直是一个瓶颈。 移动研发在最近两年可以说越来越趋于稳定&#xff0c;因为越来越多人开始学习Android开发&…

掌握这些Android开发热门前沿知识,跳槽薪资翻倍

前言 这是一篇软文、但是绝对不是鸡汤&#xff1b;为啥不是呢&#xff1f;因为我文笔太差…偶尔矫情发发牢骚&#xff08;勿喷&#xff09; 说说程序猿行业 现在社会上给IT行业贴上了几个标签&#xff1a;高薪、高危、高大上、秃顶&#xff08;哈哈&#xff09;。这些标签我…

linux环境-docker安装rabbitmq

1、进入docker hub镜像仓库地址&#xff1a;https://hub.docker.com/ 2、搜索rabbitMq&#xff0c;进入官方的镜像&#xff0c;可以看到以下几种类型的镜像&#xff1b;我们选择带有“mangement”的版本&#xff08;包含web管理页面&#xff09;&#xff1b; 3、拉取镜像 doc…

揭秘ARouter路由机制,源码+原理+手写框架

前言 每个程序员都有一个梦想&#xff0c;那就是进一线互联网公司深造&#xff0c;不要跟我说你不想进去&#xff0c;如果给你一个这样的平台&#xff0c;不管是薪资待遇还是接触的高度来说&#xff0c;对我们程序员来说都是一个机会&#xff0c;我以前有一个同事&#xff0c;…

揭秘!双非渣本Android四年磨一剑,学习路线+知识点梳理

第一次观看我文章的朋友&#xff0c;可以关注、点赞、转发一下&#xff0c;每天分享各种干货技术和程序猿趣事 由于涉及到的面试题较多导致篇幅较长&#xff0c;我根据这些面试题所涉及到的常问范围总结了并做出了一份学习进阶路线图​​​​​​​及面试题答案免费分享给大家&…

Windows上PostgreSQL安装配置教程

这篇文章主要为大家详细介绍了Windows上PostgreSQL安装配置教程&#xff0c;具有一定的参考价值&#xff0c;感兴趣的小伙伴们可以参考一下 PostgreSQL的扩展PostGIS是最著名的开源GIS数据库。 安装PostgreSQL是第一步。 1.下载PostgreSQL的二进制安装文件。 PostgreSQL官网…

快递100接口的调用过程

前言 大部分的商城都需要调用快递的接口来记录商城的物流信息&#xff0c;这里就给出一种快递接口&#xff08;快递100&#xff09;调用的方法。 正文 一、官方文档 1. 官方文档的地址为&#xff1a; https://www.kuaidi100.com/openapi/api_subscribe.shtml 二、具体实现 1. 商…

搞懂开源框架设计思想真的这么重要吗?终获offer

正文 从我个人的角度写写30多岁码工的感受&#xff1a;的确是受年龄压力开始增大了。比如二十多岁的小年轻&#xff0c;可能什么都懂&#xff0c;对组里的东西很熟悉。有时候我也怀疑自己是不是智商不够&#xff0c;是不是自学能力太差&#xff0c;是不是基础不行&#xff0c;…

真香定律!Android动态换肤实现原理解析,吐血整理

自己项目中一直都是用的开源的xUtils框架&#xff0c;包括BitmapUtils、DbUtils、ViewUtils和HttpUtils四大模块&#xff0c;这四大模块都是项目中比较常用的。最近决定研究一下xUtils的源码&#xff0c;用了这么久总得知道它的实现原理吧。我是先从先从BitmapUtils模块开始的。…