当前位置: 首页 > 编程日记 > 正文

深入理解 python 元类

一、什么的元类

# 思考:
#     Python 中对象是由实例化类得来的,那么类又是怎么得到的呢?
# 疑问:
#     python 中一切皆对象,那么类是否也是对象?如果是,那么它又是那个类实例化而来的呢?
class student(object):def __init__(self):passjmz = student()print(type(jmz))  # <class '__main__.student'>   实例化student 得来的
print(type(student))    # <class 'type'>   
# 说明student类 是实例化type得来的。但任然是类
# 总结:  元类即类的类。  type 就是Python中的默认元类
# 元类===》类====》对象 (对象是实例化类得来的,类是实例化元类得来的)

二、类的产生过程

2.1 类的三大关键组成部分

1、类名

2、类的基类们

3、类的名称空间

  2.2 type讲解

在Python中type 函数有两种完全不同 的用法,一个是用来返回对象的类,一个则是动态的创建类。(我知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

2.3 type的两种用法

# type用法一: 查看对象的类
class dog(object):def __init__(self):passdef eat(self):print('dog eating...')dog1 = dog()
dog1.eat()          # dog eating...
print(type(dog1))   # <class '__main__.dog'>
print(dog.__dict__)
# {'__module__': '__main__', '__init__': <function dog.__init__ at 0x00000000021B9950>, 'eat': <function dog.eat at 0x00000000021B99D8>, '__dict__': <attribute '__dict__' of 'dog' objects>, '__weakref__': <attribute '__weakref__' of 'dog' objects>, '__doc__': None}# type 用法二: 创建动态类
def __init__(self):pass
def eat(self):print("cat eating...")
cat = type('cat',(object,),{"__init__":__init__,"eat":eat})cat1 = cat()
cat1.eat()          # cat eating...
print(type(cat1))  # <class '__main__.cat'>
print(cat.__dict__)
# {'__init__': <function __init__ at 0x00000000004D1EA0>, 'eat': <function eat at 0x00000000022D9A60>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'cat' objects>, '__weakref__': <attribute '__weakref__' of 'cat' objects>, '__doc__': None}
# 分析:
#     1、dog 类 与cat类功能基本一致,只是实现方式不同,一个使用了class 定义了类,一个使用了type产生了一个类
#     2、dog类与cat类实例化方式一样,
#     3、产生对象后的调用对象方法一样
#     4、__dict__ 类的内容基本一致# 总结:
#     class 关键字的底层实现就是做了与type方法一样的事

2.4、 class关键字底层实现

#1 拿到类名class_name="cat"
#2 拿到基类们class_bases=(object,)
#3 拿到名称空间 class_dic={...}
#4 调用元类产生cat类,
cat=type(class_name,class_bases,class_dic)

三、元类的使用

3.1 自定义元类

Python 中默认元类就是type,所以要自定义元类就需要继承元类,继承元类的类任然是元类 

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# Author Jmzclass Mymeta(type):   # 继承type元类def __init__(self,class_name,class_bases,class_dict):if not class_name.istitle():raise TypeError('类的名称首字母必须大写')if not class_dict.get('__doc__'):raise TypeError('类的定义必须要有参数')super(Mymeta, self).__init__(class_name, class_bases, class_dict)# 使用mateclass
class People(object,metaclass=Mymeta):       # ===》 People = type("People",(object,),{名称空间})'''这是一个关于人的类'''def __init__(self,name,sex):self.name = nameself.sex = sexdef do(self,thing):print("%s 正在做%s"%self.name,thing)

3.2 自定义元类产生类

通过对于元类的创建,我们可以做到控制元类,约束类的创造。

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# Author Jmzclass Mymeta(type):   # 继承type元类def __init__(self,class_name,class_bases,class_dict):  # class_name="People",class_bases=(object,),class_dict={名称空间}if not class_name.istitle():raise TypeError('类的名称首字母必须大写')if not class_dict.get('__doc__'):raise TypeError('类的定义必须要有参数')super(Mymeta, self).__init__(class_name, class_bases, class_dict)# 使用mateclass
class People(object,metaclass=Mymeta):       # ===》 People = type("People",(object,),{名称空间})'''这是一个关于人的类'''def __init__(self,name,sex):self.name = nameself.sex = sexdef do(self,thing):print("%s 正在做%s"%self.name,thing)

3.3 __call__ 方法

对象是由类调用产生的,对象被当成方法调用,会触发类的__call__方法

class teacher(object):__addr ="上海校区"def __init__(self,name):self.name = namedef select_class(self):passdef __call__(self, *args, **kwargs):   # 对象当成方法调用时会触发类的__call__ 方法执行print("对象调用触发")print(args)print(kwargs)egon = teacher('egon')
egon(23,"",school = "oldboy")   
# 对象调用触发
# (23, '男')
# {'school': 'oldboy'}

   

    类是由元类调用产生的,那么类被当成方法调用也应该触发元类的__call__方法

class Mymeta(type):def __call__(self, *args, **kwargs):print("我是元类的__call__ 方法,类被调用时触发")print(args)print(kwargs)class Student(object,metaclass=Mymeta):   # Student = type("Student",(object,),{})passStudent('jmz',18,school="交大")
# 我是元类的__call__ 方法,类被调用时触发
# ('jmz', 18)
# {'school': '交大'}

从上面的代码我们可以看出,对象的产生其实就是,调用了类,进而触发了元类的__call__ 方法,

   但是调用类产生的是对象,说明元类的__call__ 方法是用来产生对象的。

说明元类的__call__ 方法就做了下面的几件事

1、创造了一个空对象

2、调用了类的__init__ 方法

3、将参入传入__init__方法中

3.4 自定义元类产生对象

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# Author Jmzclass Mymeta(type):  def __init__(self,class_name,class_bases,class_dict):if not class_name.istitle:raise TypeError("类的首字母必须大写")if not class_dict.get("__doc__"):raise TypeError("类的创建需写入注释说明")super(Mymeta,self).__init__(class_name,class_bases,class_dict)def __call__(self, *args, **kwargs):   # __call__  是由类调用的,所以self 就是类本身# 1、 产生一个空对象# 2、 将参数传入类的__init___方法中
obj = object.__new__(self)   # 产生一个空对象,, __new__ 是object 的静态方法self.__init__(obj,*args,**kwargs)  # 调用类的__init__ 方法,self 就是类return objclass Student(object,metaclass=Mymeta):'''这是学生类'''__school = "上海交大"def __init__(self,name,age):self.name = nameself.age = agedef get_school(self):return self.__schooljmz = Student("jmz",24)
print(jmz.__dict__)
print(jmz.get_school())
# {'name': 'jmz', 'age': 24}
# 上海交大
# 上文解释:
   1、元类的__call__ 方法是由类被调用触发的(即类(),就会触发),2、类是实例化元类产生的,也可以说是元类产生的对象就是类,触发元类的__call__ 方法就是由类这个对象调用触发的,所以元类中的self 就是类。3、将参数传入类的__init__ 方法其实就是将参数传入元类中self的__init__方法。4、调用类的__init__ 方法与调用静态方法相同,该传的参数都要传

四、为什么要使用元类

从上面的如何使用元类中,我们可以看出,通过对于元类的定义,我们可以控制类的产生,和对象的产生。

五、单例模式

5.1 类实现单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# Author Jmzclass mysql(object):__instance = Nonedef __init__(self):pass@classmethoddef get_instance(cls,*args,**kwargs):if not cls.__instance:cls.__instance = cls(*args,**kwargs)return cls.__instancemysql = mysql.get_instance()
print(mysql)
# <__main__.mysql object at 0x00000000022D7BE0>
mysql = mysql.get_instance()
print(mysql)
# <__main__.mysql object at 0x00000000022D7BE0># 说明两个返回的内容地址是一样的,说明只实例化了一次。

5.2 元类实现单例模式

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# Author Jmzclass Mymeta(type):__instance = Nonedef __init__(self,class_name,class_bases,class_dict):if not class_name.istitle:raise TypeError("类的首字母必须大写")if not class_dict.get("__doc__"):raise TypeError("类的创建需写入注释说明")super(Mymeta,self).__init__(class_name,class_bases,class_dict)def __call__(self, *args, **kwargs):   # __call__  是由类调用的,所以self 就是类本身# 1、 产生一个空对象# 2、 将参数传入类的__init___方法中if not self.__instance:obj = object.__new__(self)   # 产生一个空对象,, __new__ 是object 的静态方法self.__init__(obj,*args,**kwargs)  # 调用类的__init__ 方法,self 就是类self.__instance = objreturn self.__instanceclass Student(object,metaclass=Mymeta):'''学生类'''def __init__(self,name,age,school):self.name = nameself.age = ageself.school = schooldef learn(self,thing):print("%s 正在学习%s"%(self.name,thing))class Teacher(object,metaclass=Mymeta):'''老师类'''def __init__(self,name,age):self.name = nameself.age = agedef teach(self,):print("%s 正在教书中。。。"%self.name)jmz = Student("jmz",23,"北大")
jmz1 = Student("jmz1",23,"北大")print(id(jmz),id(jmz1))
# 38853264 38853264
egon = Teacher("egon",24)
egon1 = Teacher("egon1",24)
print(id(egon),id(egon1))
# 31578992 31578992# 解释
# 1、student 类和 teacher类都是调用元类产生的,不同的类调用元类就好产生不同的内容地址。不同的类也只会定义一次(正常的)
# 2、 对象的产生是有调用了元类的__call__ 方法产生的,所以每次调用都返回相同的对象(单例)。
元类,单例实现

转载于:https://www.cnblogs.com/xiaobaiskill/p/9482586.html

相关文章:

使用.NET REACTOR制作软件许可证

使用.NET REACTOR制作软件许可证 原文:使用.NET REACTOR制作软件许可证软件下载地址&#xff1a;http://www.eziriz.com/downloads.htm 做一个简单的许可证系统&#xff0c;下面是具体步骤&#xff1a;1&#xff0c; OPEN ASSEMBLY打开项目可执行文件(debug文件夹里面exe文件…

(C++)CSP 201712-2 游戏

#include<cstdio> #include<algorithm> using namespace std;const int M 1000;int k;bool obsl(int x){if(x%k0||x%10k){return true;//淘汰 }else return false; }int main(){int n;//孩子的个数 scanf("%d%d",&n,&k);int i1;//现在报的数 in…

在wpf中运行EXE文件

最简单的方法&#xff1a;System.Diagnostics.Process.Start("路径");网上的其他方法&#xff1a; Process p new System.Diagnostics.Process(); p.StartInfo.FileName "路径"; p.StartInfo.Arguments ""; …

C语言程序试题

一个无向连通图G点上的哈密尔顿&#xff08;Hamiltion&#xff09;回路是指从图G上的某个顶点出发&#xff0c;经过图上所有其他顶点一次且仅一次&#xff0c;最后回到该顶点的路劲。一种求解无向图上哈密尔顿回路算法的基础实现如下&#xff1a; 假设图G存在一个从顶点V0出发的…

利用OWC创建图表的完美解决方案

http://onlytiancai.cnblogs.com/archive/2005/08/24/221761.html 转载于:https://www.cnblogs.com/Athrun/archive/2008/05/19/1202909.html

(C++)1020 月饼 简单贪心

#include<cstdio> #include<algorithm> using namespace std;int types,weight;//月饼的种类数 struct Mooncake{double totalPrice;double price;double weight;double sell;//卖出了多少 };bool cmp(Mooncake a,Mooncake b){return a.price>b.price; }int ma…

枚举,给枚举赋值

/**************枚举*****************/// public enum Colors{// Red,Yellow,Blue,Black,White// }// public static void main(String[] args) {// Colors c Colors.Yellow;// System.out.println(c);//输出枚举// System.out.println(c.ordinal());//输出枚举对应的序号…

青岛...沙尘暴!太可怕了~什么事儿都有!

受蒙古国和我国内蒙古地区出现沙尘暴天气的影响&#xff0c;28日&#xff0c;山东省青岛、烟台等地出现大范围浮尘天气&#xff0c;空气质量明显下降。 28日&#xff0c;一场大范围的浮尘天气影响到烟台&#xff0c;天空一片浑浊&#xff0c;能见度不足5公里&#xff0c;空气质…

面试题收集最新

Java高级程序员面试题------https://www.cnblogs.com/mengdou/p/7233398.html Java高级工程师面试题总结及参考答案-----https://www.cnblogs.com/java1024/p/8594784.html Java高级程序员&#xff08;5年左右&#xff09;面试的题目集----https://blog.csdn.net/fangqun663775…

(C++)1023 组个最小数 简单贪心

#include<cstdio> //#include<algorithm> //using namespace std; //用hash思想读入数字 //解决最高位放谁 //解决后面的位数 //输出 int main(){int key[10];for(int i0;i<10;i){scanf("%d",&key[i]);}//解决最高位for(int i1;i<10;i){if(ke…

Nginx 在centos linux 安装、部署完整步骤并测试通过

需要先装pcre, zlib&#xff0c;前者为了重写rewrite&#xff0c;后者为了gzip压缩。 1.选定源码目录 选定目录 /usr/local/ cd /usr/local/ 2.安装PCRE库 cd /usr/local/ wget http://exim.mirror.fr/pcre/pcre-8.02.tar.gz tar -zxvf pcre-8.02.tar.gz cd pcre-8.02 ./config…

Ubuntu16.04安装qt

5.11官方下载网站&#xff1a; http://download.qt.io/official_releases/qt/5.11/5.11.1/ 可以直接下载linux系统下的.run安装包&#xff1a; 安装方式&#xff1a;https://www.jb51.net/LINUXjishu/501994.html 切换到.run所在的目录&#xff0c;然后 第一步&#xff1a; chm…

好男人是怎么变坏的

十岁以前&#xff0c;就不说了&#xff0c;无非是淘气和不懂事。 十三、四岁的时候&#xff0c;开始对女孩有好感&#xff0c;但是那时候他离女孩远远的&#xff0c;并且以讨厌女孩自居&#xff0c;生怕被同伴嘲笑。 十五岁的时候&#xff0c;听到大人们说某某男人好花&#xf…

(C++)小明种苹果(续)

#include<cstdio>struct tree{int left;//剩余的果子数量bool fallfalse;//是否发生掉落int falls0;//这颗数前面的树&#xff08;包括自身&#xff09;发生掉落的次数 }trs[1000];int main(){int n;//树的总数scanf("%d",&n);for(int i0;i<n;i){//对于…

MySQL如何判别InnoDB表是独立表空间还是共享表空间

InnoDB采用按表空间&#xff08;tablespace)的方式进行存储数据, 默认配置情况下会有一个初始大小为10MB&#xff0c; 名字为ibdata1的文件&#xff0c; 该文件就是默认的表空间文件&#xff08;tablespce file&#xff09;&#xff0c;用户可以通过参数innodb_data_file_path对…

如何使用WindowsLiveWriter发文章

1.下载wlw最新版本http://download.microsoft.com/download/8/0/9/809604cd-bd08-42c8-b590-49c332059e64/writer.msi 2.在菜单中选择“Weblog”&#xff0c;然后选择“Another Weblog Service”。如图一 &#xff08;图一&#xff09; 3.在Weblog Homepage URL中输入你的Blog主…

很多学ThinkPHP的新手会遇到的问题

在模板传递变量的时候&#xff0c;很多视频教程都使用$v.channel的方式&#xff0c;如下&#xff1a; <a href"{:U(Chat/set,array(id>$v.channel))}" title"设置" class"btn btn-mini tip"> 这会导致URL在解析的时候出现问题&#xff…

(C++)1040 有几个PAT

#include<cstdio> #include<cstring> const int MOD 1000000007; const int maxn 100010;int main(){char str[maxn];scanf("%s",str);int len strlen(str);//数出每个元素左侧的P的个数int leftnumP[maxn];leftnumP[0] 0;for(int i1;i<len;i){if…

C#进行Visio二次开发之电气线路停电分析逻辑

停电分析&#xff0c;顾名思义&#xff0c;是对图纸进行停电的逻辑分析。在电气化线路中&#xff0c;一条线路是从一个电源出来&#xff0c;连接着很多很多的设备的&#xff0c;进行停电分析&#xff0c;有两个重要的作用&#xff1a;一是看图纸上的Shape元件是否连接正常&…

红芯丑闻揭秘者 Touko 专访 | 关于红芯丑闻的更多内幕……

专栏 | 九章算法 网址 | www.jiuzhang.com ❤ 红芯事件 近日&#xff0c;一则《自主研发的国产浏览器内核&#xff0c;红芯宣布获2.5亿C轮融资》的讯息再次将“国产自主创新”这一话题推向高潮&#xff0c;希冀之声群起。然好景不长&#xff0c;网友Touko在将红芯浏览器的exe文…

数学图形(1.20)N叶草

有N个叶子的草 相关软件参见:数学图形可视化工具,使用自己定义语法的脚本代码生成数学图形.该软件免费开源.QQ交流群: 367752815 vertices 1000 t from 0 to (2*PI) r 10 n rand_int2(3, 10) p 1 cos(n*t) sin(n*t)^2 x p*cos(t) y p*sin(t) N叶草面_1 vertices D1:5…

(C++)1045 快速排序 非满分

#include<cstdio>const int maxn100010; //思路&#xff0c;从第一个元素开始&#xff0c;假设其是主元&#xff0c;然后用two pointers方法&#xff0c;看有没有进行交换&#xff0c;进行了则不是 int main(){int iszy[maxn]{0};//0表示可以是主元&#xff0c;1表示一定…

android笔记1——开发环境的搭建

Long Long ago...已经成为了历史&#xff0c;我还是要说出一个真相&#xff1a;早年前&#xff0c;那时候&#xff0c;android还不被大众所认知的时候&#xff0c;当然开发人员也没不像如今那样趋于饱和状态。一位大牛前辈&#xff0c;也是我的学长&#xff0c;那时候我还在上大…

XP的几个小问题

一&#xff1a;没有了显示桌面可以用WinD来实现&#xff0c;但是用鼠标习惯了&#xff0c;所以还是让它出现好&#xff1a;点击“开始→运行”&#xff0c;在弹出的“运行”对话框中输入“REGSVR32 /n /i:u shell32”&#xff08;不含双引号&#xff09;&#xff0c;然后回车&a…

使用 SQL Server 的 uniqueidentifier 字段类型

原文:使用 SQL Server 的 uniqueidentifier 字段类型SQL Server 自 2008 版起引入了 uniqueidentifier 字段&#xff0c;它存储的是一个 UUID, 或者叫 GUID&#xff0c;内部存储为 16 个字节。SQL Server 可用两个函数来生成 uniqueidentifier, 分别是 NEWID() 和 NEWSEQUENTIA…

(C++)202012-2 期末预测之最佳阈值 满分

#include<cstdio> #include<algorithm> using namespace std;const int M 100000;struct Student{int score;int res;//0表示挂科&#xff0c;1表示未挂int times;//表示预测正确的次数 int before;//在它之前的0的个数 int after;//在它之后的1的个数 }stus[M10…

javascript之prototype总结常用方法

//去左右空格String.prototype.trim function(){ return this.replace(/^\s*|\s*$/g,);}//去空格添加至数组集合String.prototype.splitrim function(t){ return this.trim().split(new RegExp(\\s*t\\s*)) }test "testing , splitrim ";var arr test.split…

Power Designer逆向工程导入Oracle表,转为模型加注释

1.打开PowerDesigner ——文件——Reverse Engineer——DataBase 2.选择所要连接数据库版本&#xff0c;此处使用的是oracle version 11g。 3.点击红色区域&#xff0c;选择数据源 4.选择modify 5.在此填写你的数据库名称、连接地址、用户名。确定 6.选择你新建立的连接数据库 …

ubuntu修改主机名后无法解析主机

修改完主机名后在执行sudo命令时&#xff0c;会提示sudo: 无法解析主机。在网上搜了下&#xff0c;找到了解决方法&#xff1a;1.sudo vim /etc/hosts找到如下行&#xff1a;127.0.1.1 XXX修改为&#xff1a;127.0.1.1 &#xff08;修改后的主机名&#xff09; 转载于:https://…

(C++)201709-1 打酱油

#include<cstdio> #include<algorithm> using namespace std;//贪心问题&#xff0c;优先级&#xff1a;剩的钱购买5瓶就买5瓶&#xff0c;不够看够不够买三瓶&#xff0c;再不够看够不够买一瓶 int main(){int start,left,num0;//初始的钱&#xff0c;当前剩下的…