元类

什么是元类

元类是类的类,是类的模板(就如对象的模板是类一样)

元类的实例为类,类的实例为对象

元类是用来产生类的

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,是运行时动态创建的

__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元类深入解析的更多相关文章

  1. python元类:type和metaclass

    python元类:type和metaclass python中一切皆对象,所以类本身也是对象.类有创建对象的能力,那谁来创建类的呢?答案是type. 1.用tpye函数创建一个类 class A(ob ...

  2. Python 元类 - Metaclasses

    Python 元类 - Metaclasses 默认情况下儿, classes 是有 type() 构造的. 类的结构体在一个新的 namespace 被执行, 类的名字 class name 绑定( ...

  3. Python进阶丨如何创建你的第一个Python元类?

    摘要:通过本文,将深入讨论Python元类,其属性,如何以及何时在Python中使用元类. Python元类设置类的行为和规则.元类有助于修改类的实例,并且相当复杂,是Python编程的高级功能之一. ...

  4. Python之元类详细解析

    一.补充内置函数isinstance和issubclass 1.isinstance是判断一个对象是不是由一个对象产生的 class Foo: pass obj=Foo() print(isinsta ...

  5. python 元类理解

    原文来自:https://segmentfault.com/a/1190000011447445 学懂元类,你只需要知道两句话: 道生一,一生二,二生三,三生万物 我是谁?我从哪来里?我要到哪里去? ...

  6. Python元类实战,通过元类实现数据库ORM框架

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第19篇文章,我们一起来用元类实现一个简易的ORM数据库框架. 本文主要是受到了廖雪峰老师Python3入门教程的启 ...

  7. python 元类

    转载自  http://blog.jobbole.com/21351/ 类也是对象 在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大 ...

  8. [python]python元类

    这两天在看Django框架,里面的filter实现原理搞不明白,最后发现跟python的元类有关系. 原文:http://stackoverflow.com/questions/100003/what ...

  9. Python元类实践--自己定义一个和collections中一样的namedtuple

    大家可能很熟悉在collections模块中有一个很好用的扩展数据类型-namedtuple. 如果你还不知道这个类型,那么请翻看标准手册. 我利用元类轻松定义一个namedtuple. 先把代码贴上 ...

随机推荐

  1. win系统下之docker安装centos7镜像

    1)    拉取镜像 输入命令docker pull centos:7 从仓库拉取centos7的镜像 2)    查看本地镜像 命令:docker images 可以查看已经把centos的镜像拉取 ...

  2. Everything可能泄漏大量电脑敏感资料

    一款好用的文件搜索工具Everything被批露出现重大问题: Everything可以打开http服务,在没有加密的情况下任何外网电脑都可以连接的. 因此,Everything可能泄漏大量电脑敏感资 ...

  3. fastjson自由:controller上指定active profile,让你想序列化什么字段就序列化什么字段

    一.前言 最近有个需求,其实这个需求以前就有,比如定义了一个vo,包含了10个字段, 在接口A里,要返回全部字段: 但是在接口B里呢,需要复用这个 vo, 但是只需要返回其中8个字段. 可能呢,有些同 ...

  4. thinkpad p1 gen2 扬声器音量异常问题解决过程

    在弹出 "用户帐户控制" 对话框时的声音明显不对,测试后发现规律:音量在30以内,1分钟内扬声器无声音发出,运行ccleaner弹出 "用户帐户控制" 对话框, ...

  5. PHP、 Ruby、Python、Java、C++、C、Objective C——编程语言之禅

    PHP是你豆蔻年华时的心上人,她是情窦初开的你今年夏天傻乎乎追求的目标.玩一玩可以,但千万不要投入过深,因为这个“女孩”有严重的问题. Ruby是脚本家族中一个非常漂亮的孩子.第一眼看她,你的心魄就会 ...

  6. vue引入百度地图 --BMap is not defined ,eslint BMap报错

    在mounted初始化地图的时候,因为异步问题会导致BMap is not defined,也就是百度的api还没完全引入或者加载完成,就已经进行地图初始化了 解决方法: 1.创建一个map.js e ...

  7. 「Usaco2005 Dec」清理牛棚(spfa秒杀线段树dp)

    约翰的奶牛们从小娇生惯养,她们无法容忍牛棚里的任何脏东西. 约翰发现,如果要使这群有洁癖的奶牛满意,他不得不雇佣她们中的一些来清扫牛棚, 约翰的奶牛中有N(1≤N≤10000)头愿意通过清扫牛棚来挣一 ...

  8. LINUX 内核移植以及网卡驱动添加

    我用的板子是sama5d3xek,原来板子内核是linux-at91-3.13,升级使用linux-at91-4.10 首先去官网下载一个linux—at91-4.10压缩包,然后在ubuntu里解压 ...

  9. ubuntu下minicom和usb串口转接

    ubuntu下minicom和USB转串口(转) (2013-03-23 21:07:54) 转载▼ 标签: it 分类: 嵌入式linux minicom是linux下串口通信的软件,它的使用完全依 ...

  10. PHP创建文件命名中文乱码解决的方法

    PHP创建文件命名中文乱码解决的方法 <pre>iconv('utf-8', 'gbk', $dir); </pre> 因为系统环境是gbk 所以里面的字符也要gbk 编码一致 ...