python元类深入解析
元类
什么是元类
元类是类的类,是类的模板(就如对象的模板是类一样)
元类的实例为类,类的实例为对象
元类是用来产生类的
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,是运行时动态创建的
__new__()
我们之前说类实例化第一个调用的是__init__
,但__init__
其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 __new__
方法。
__new__
方法接受的参数虽然也是和__init__
一样,但__init__
是在类实例创建之后调用,而 __new__
方法正是创建这个类实例的方法。
class A:
pass
class B(A):
def __new__(cls):
print("__new__方法被执行")
def __init__(self):
print("__init__方法被执行")
b = B()
__new__方法被执行
__call__()
当类中有__call__
方法时,实例对象直接加括号,会执行这个方法,你拿到的实例对象就是它返回的
class zx():
def __call__(self,name,age):
print(name)
print(age)
zx125=zx()
zx125("wl",18)
wl
18
内置函数type()
type()
函数既可以返回一个对象的类型,又可以创建出新的类型
创建一个新的class
时需要传入3个参数:
1.class的名称
2.继承父类的集合(元组)
3.函数名和函数对象的键值对(字典)
通过type()
函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()
函数创建出class。
def speank(self):
print("llllllllllll")
zx=type("zx",(object,),dict(say=speank))
zx1=zx()
zx1.say()
print(type(zx1))
print(type(zx))
llllllllllll
<class 'main.zx'>
<class 'type'>
内置函数exec()
执行字符串的代码,当成python解释器,把代码丢给解释器解释
exec(s,g,l)
g表示当前的一个全局名称空间,当前的一个l局部名称空间,代码解析完毕,会按属性是全局和局部,放入以上参数
例1
cmd = """
x=1
print('exec函数运行了')
def func(self):
pass
"""
class_dic = {}
# 执行cmd中的代码,然后把产生的名字丢入class_dic字典中
exec(cmd, {}, class_dic)
print(class_dic)
{'x': 1, 'func': <function func at 0x10a0bc048>}
例2
x = 10
expr = """
z = 30
sum = x + y + z
print(sum)
"""
y = 20
m={'x':1,'y': 2}
w={'y': 3, 'z': 4}
exec(expr)
exec(expr,m)
exec(expr,m,w)
print(w)
60
33
34
{'y': 3, 'z': 30, 'sum': 34}
元类
class关键字
例1 理解class关键字
可见这时候已经生成了一个zx类对象了,zx的属性字典已经被写入了,那么这个过程中到底发生了什么呢?接下来我们慢慢拆解
class Mytype(type):
pass
class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
pass
print(zx.__dict__)
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'zx' objects>, '__weakref__': <attribute '__weakref__' of 'zx' objects>, '__doc__': None}
例2
创建一个对象,说到底就是用了,上面介绍的方法__init()
,__new__()
,__call()
这三个方法,真正创建对象的只有type()
这个内置方法。下面直接进入主题,直接重写元类的三个方法。
1.首先,会运行到class Mytype(type)
同理可得,这个时候会生成一个Mytype
对象,这是一个关键,自定义的元类创建好了,下面生产类对象就靠它了,这个元类的产生过程是修改不了的,因为是c实现的
2.然后就和例1
一样zx=Mytype("zx",(object,),{})
,接下来就是三个方法的运用了。首先是Mytype后面加的括号,所以去执行type
的__call__()
方法
3.然后看一下__call__()
方法的参数self, *args, **kwargs
,有个self
,这个self
就是第一步创建的那个Mytype
对象,然后会依次执行self.__new__()
和self.__init__()
方法,也就是Mytype
中的这两个方法,这两个方法的作用,主要就是创建对象,给对象填入相关的属性,而__call__()
主要就是对操作的封装,然后返回组装完成的类对象
4.我们看到结果,只打印了new
,但是按上面的步骤,不是还应该打印一个init
的吗?别急,在看打印生成的对象zx
它竟然是个None
,所以其中必要蹊跷,发生了什么呢?其实过程还是和上面一样的,只不过我们重写了方法,导致运行到__new__()
的时候,这个方法,返回了一个None
,这怎么行呢?所以当返回值返回到__call__()
的时候,导致直接出了异常,方法中断了,但是没有打印异常,是因为源码做了处理,返回了一个None
class Mytype(type):
def __init__(self,*args, **kwargs):
print("init")
def __call__(self, *args, **kwargs):
print("call")
def __new__(cls, *args, **kwargs):
print("new")
class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
pass
print(zx)
new
None
例3 实现创建类对象
出现了这样的问题当然要解决他呢!首先要注意类对象是由自定义元类生成的,实例对象是由类对象生成的,这两种情况的方法执行流程是相同的,但是有些细节的偏差
1.我把打印结果移到了,__init__()
方法去了,但是发现执行这个方法的时候,我们想要的zx
类对象其实已经创建好了,这就是主要的区别,生成类对象,和属性填充等操作都会放在__new__()
里面,而创建实例对象的时候,一些添加属性的过程,则会放到__init__()
里面
2.在__new__()
方法中,我创建对象用的方法是type.__new__(cls,*args, **kwargs)
,为啥呢?因为我们自定义元类的时候,如果没有重写__new__()
方法是时候,他就找不到这个方法,这个时候当然就是去父类找这个方法,那Mytype
的父类是谁呢?就是type
,其实原理是一样的
3.我们成功自定义了元类,并且拿到了zx
类对象,我们可以发现整个过程我们都是可以控制的,自定义元类给我们极大的操作过程,那么接下来我们来操作一下实例对象生成的过程
class Mytype(type):
def __init__(self,*args, **kwargs):
print(self)
print(self.__dict__)
print("init")
def __call__(self, *args, **kwargs):
print("call")
def __new__(cls, *args, **kwargs):
# print(cls)
# return object.__new__(cls)
return self.__new__(cls,*args, **kwargs)
class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
pass
<class '__main__.zx'>
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'zx' objects>, '__weakref__': <attribute '__weakref__' of 'zx' objects>, '__doc__': None}
init
例4 实例对象的创建过程
这里我把Mytype
的__init__()
和__new__()
方法去掉,让他直接调用默认的方法就行,这里不研究类对象的生成了
1.首先来查看打印结果,一个为空,这就是上面所说的区别,在创建实例对象的时候,使用的是不是type
的__new__方法
,而是,找zx
继承类,一般都是创建一个空对象,除非你自定义一个父类,从写它的__new__()
方法
2.创建完了一个空对象,然后调用__init__()
方法给他进行填值,最后我们拿到了自己想要的实例对象了
class Mytype(type):
def __call__(self, *args, **kwargs):
obj=self.__new__(self, *args, **kwargs)
print(obj.__dict__)
obj.__init__( *args, **kwargs)
return obj
class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
def __init__(self,name,age):
self.name=name
self.age=age
def run(self):
print("runrun")
ha=zx("小明",18)
print(ha.__dict__)
{}
{'name': '小明', 'age': 18}
总结
1.一切皆对象
2.注意自定义元类的特殊性,他是父类是type
,在创建类对象的时候他的__new__()
方法,直接把创建对象和添加属性字典的操作都完成了
3.注意对象+()调用的是,元类!的__call()
方法,默认元类都是type
4.太难了,太绕了,记一下,容易忘
__new__()
一共接收4个参数(也可以使用位置形参接收)
底层就是调用了type()
方法
分别为:
1.当前准备创建的类对象;
2.类的名字
3.类继承的父类集合
4.类的方法集合
python元类深入解析的更多相关文章
- python元类:type和metaclass
python元类:type和metaclass python中一切皆对象,所以类本身也是对象.类有创建对象的能力,那谁来创建类的呢?答案是type. 1.用tpye函数创建一个类 class A(ob ...
- Python 元类 - Metaclasses
Python 元类 - Metaclasses 默认情况下儿, classes 是有 type() 构造的. 类的结构体在一个新的 namespace 被执行, 类的名字 class name 绑定( ...
- Python进阶丨如何创建你的第一个Python元类?
摘要:通过本文,将深入讨论Python元类,其属性,如何以及何时在Python中使用元类. Python元类设置类的行为和规则.元类有助于修改类的实例,并且相当复杂,是Python编程的高级功能之一. ...
- Python之元类详细解析
一.补充内置函数isinstance和issubclass 1.isinstance是判断一个对象是不是由一个对象产生的 class Foo: pass obj=Foo() print(isinsta ...
- python 元类理解
原文来自:https://segmentfault.com/a/1190000011447445 学懂元类,你只需要知道两句话: 道生一,一生二,二生三,三生万物 我是谁?我从哪来里?我要到哪里去? ...
- Python元类实战,通过元类实现数据库ORM框架
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第19篇文章,我们一起来用元类实现一个简易的ORM数据库框架. 本文主要是受到了廖雪峰老师Python3入门教程的启 ...
- python 元类
转载自 http://blog.jobbole.com/21351/ 类也是对象 在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大 ...
- [python]python元类
这两天在看Django框架,里面的filter实现原理搞不明白,最后发现跟python的元类有关系. 原文:http://stackoverflow.com/questions/100003/what ...
- Python元类实践--自己定义一个和collections中一样的namedtuple
大家可能很熟悉在collections模块中有一个很好用的扩展数据类型-namedtuple. 如果你还不知道这个类型,那么请翻看标准手册. 我利用元类轻松定义一个namedtuple. 先把代码贴上 ...
随机推荐
- Java内存模型相关原则详解
在<Java内存模型(JMM)详解>一文中我们已经讲到了Java内存模型的基本结构以及相关操作和规则.而Java内存模型又是围绕着在并发过程中如何处理原子性.可见性以及有序性这三个特征来构 ...
- 学习笔记22_AspMvc简介
*Mvc和webForm区别 1. Mvc模式下,前台和后台的交流,是后台提供数据,使用对象包裹的形式,前台来使用,类似于webForm定义一个属性那样. 2.Mvc模式下,再也不是使用this.la ...
- CSPS模拟 85
WWB大佬的bitset映射真是太强了! %%% T1 观察样例,猜规律. T2 对题目的翻译工作用了很长时间 翻译错了好几次.. 观察到奇环没法染色,选的边必须把奇环弄断 如果在偶环上,偶环就变得没 ...
- 使用webpack+babel构建ES6语法运行环境
1.前言 由于ES6语法在各个浏览器上支持的情况各不相同,有的浏览器对ES6语法支持度较高,而有的浏览器支持较低,所以为了能够兼容大多数浏览器,我们在使用ES6语法时需要使用babel编译器将代码中的 ...
- 爬虫学习--Day4(网页采集器的实现)
#UA: User-Agent {请求载体的身份标识}#(反爬机制)UA检测:门户网站的服务器回检测对应请求的载体身份标识,如果检测到请求的载体身份为某一款浏览器就说明该请求时一个正常的请求.但是,如 ...
- Project Euler 54: Poker hands
在纸牌游戏中,一手包含五张牌并且每一手都有自己的排序,从低到高的顺序如下: 大牌:牌面数字最大 一对:两张牌有同样的数字 两对:两个不同的一对 三条:三张牌有同样的数字 顺子:所有五张牌的数字是连续的 ...
- 最适合Java开发者的一本书和一软件
一书-<Java编程思想> 一软件-IntelliJ IDEA Java自学是否可以成功,答案显而易见,可以. 自学Java关键看自己是否有毅力.是否有恒心. 自学Java 自学Java不 ...
- 【html css js】实现一个简易日历
——[效果预览] 实现了日历最基础的功能,当前日期红色显示,可通过上方的左右按钮查看上一月或下一月的日期. ——[代码部分] 1. HTML <body> <div class=&q ...
- 随机点名小程序--- -JAVA版本
话不多少,直接上代码 一个能够直接运行的随机点名的小程序,一个界面化的小程序.望广大网友多多支持! 1.创建一个随机点名的类 public class ProcessRandomName { JFra ...
- jsp页面时间的转换js
/** * 日期 转换为 Unix时间戳 * @param <string> 2014-01-01 20:2 ...