一、前言:

要搞懂元类必须要搞清楚下面几件事:

  • 类创建的时候,内部过程是什么样的,也就是我们定义类class 类名()的过程底层都干了些啥

  • 类的调用即类的实例化过程的了解与分析

  • 我们已经知道元类存在的情况下的属性查找新顺序分析

-------------------------------------------------------------------------------------------------------------------------------------------------

1、先来认识认识类的创建过程:

class Newclass():   # class定义一个类,类名为:Newclass
# 下面的代码都是类体代码,也就是类的属性们
def __init__(self, name, age):
self.name = name
self.age = age
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name)

  当我们用class声明要创建一个类的时候,实际上内部流程是这样的(以上面的为例):

  ①.定义类名Newclass:class_name = 'Newcalss'

  ②.设定这个类Newclass的父类(基类)们(一个类可以继承多个类):class_bases = (object,) 不设定默认继承object

  ③.执行类体代码,拿到类的名称空间:class_dic = {...} (这里的字典就是我们前面学习类的时候查看类里面的属性方法.__dict__的内容。

2、我们调用创建的类Newclass(也就是实例化对象)的过程:

t1 = Newclass('sgt', 30)  # 调用类,实例化t1这个对象

通过调用类Newclass实例化出对象t1时,会发生以下三件事:

  ①.先产生一个空的对象obj

  ②.调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中

  ③.将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象

二、开始认识元类

1、类产生的过程分析:

  还是先定义一个类来分析分析:

class Newclass():
def __init__(self, name, age):
self.name = name
self.age = age
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name)
t1 = Newclass('sgt', 30) 

  首先,所有的对象都是实例化或者说调用类而得到的(调用类的过程称之为实例化对象),比如对象t1是调用类Newclass得到的

  如果一切皆对象,那么Newclass本质也可以看成一个对象,既然所有的对象都是调用类得到的,那么是不是可以大胆的想象我们声明class创建一个类Newclass的时候,是否也是调用一个更高级的类得到的呢?

  事实就是我们想的那样,这个‘实例化类的类’就是我们今需要好好了解的‘元类’。

  于是我们可以大致有这么过程:产生Newclass的过程一定发生了:Newclass = 元类(...)

# 我们来分别打印一下实例化出的对象t1和创建的类Newclass的类型:
print(type(t1))
print(type(Newclass))
# 结果是:
# <class '__main__.Newclass'>
# <class 'type'>
# 我们可以推出:
# t1是调用Newclass产生的
# Newclass是调用type产生的

  一开始我们就提前了解了class出一个类Newclass时候发生的过程,这里再补充一下,如果我们不用class创建类的另外一种原始方法:

# 引入函数exec(object, globals, locals)
exec用法:
object:类体代码,包含一系列python代码的字符串
globals:全局作用域(字典形式),如果不指定,默认为globals()
locals:局部名称空间(类的名称空间)
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中 # 不依赖class关键字创建一个自定义类
类名class_name= 'Newcalss'
继承的类们:class_bases = (object,)
类体代码:class_body = '''
def __init__(self, name, age):
self.name = name
self.age = age
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name) ''' exec(class_body,{},class_dic) # 创建一个名称空间,将类体代码放入class_dict中,这个class_dict就代表exec创建的名称空间
# 调用type得到自定义的类:
Newclass = type(class_name, class_bases, class_dict)

2、自定义元类,控制类Newclass的创建过程

  既然知道了类的创建过程,那么我们就可以自定义元类来控制类的创建

  首先:一个元类如果没有声明自己的元类,默认它的元类就是type,出了使用内置元类type,我们也可以通过集成type来自定义元类,然后使用metaclass关键字参数为一个类指定元类

class MyMeta(type):  # 只有继承了type的类才能称之为一个元类,否则就是普通的类
pass class Newclass(object,metaclass=MyMeta): # 继承基类object,为Newclass指定元类为MyMeta
def __init__(self, name, age):
self.name = name
self.age = age coutry = 'China' def task(self):
print('%s is sleeping' % self.name)

  然后:自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即:

  Newclass = Mymeta(‘Newclass’,(object,),{.....}),

  一开始我们先预习了,调用类是发生的过程:同理调用MyMeta会先产生一个空对象Newclass,然后连同MyMeta括号内的参数一同传给MyMeta下的__init__方法,完成初始化,所以我们可以在这个过程中做一下事情:

class MyMeta(type):  # 只有继承了type的类才能称之为一个元类,否则就是普通的类
def __init__(self, class_name, class_bases, class_dict):
super().__init__(class_name, class_bases, class_dict) if class_name.islower(): # 给类名做限制,必须为驼峰体,否则抛异常
raise TypeError('类名必须为驼峰体')
# 给类中注释的存在性加以限制,必须要有注释且不能为空,否则抛异常
if '__doc__' not in class_dict or len(class_dict['__doc__'].strip(' \n')) == 0:
raise TypeError('类必须要求有文档注释,且不能为空') class Newclass(object,metaclass=MyMeta): # 继承基类object,为Newclass指定元类为MyMeta
'''
这是Newclass类的注释
'''
def __init__(self, name, age):
self.name = name
self.age = age coutry = 'China' def task(self):
print('%s is sleeping' % self.name)

3、自定义元类,控制类Newclass的调用过程

  一开始提过:调用类就是实例化对象,那么调用这个行为,就必须要知道__call__这个知识点,所以先来说说__call__:

class Newclass():
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name) def __call__(self, *args, **kwargs):
print('__call__被调用了>>>', self)
print('__call__被调用了>>>', args)
print('__call__被调用了>>>', kwargs)
t1 = Newclass() ## 要想让t1这个对象可调用,需要在该对象的类中定义一个__call__方法,
# 该方法会在对象t1在调用时候自动触发
## 调用t1的返回值就是__call__方法的返回值
res = t1('a', 'b', 8, name = 'jason', age = 18) # 右键运行结果:
__call__被调用了>>> <__main__.Newclass object at 0x000001AD69359828>
__call__被调用了>>> ('a', 'b', 8)
__call__被调用了>>> {'name': 'jason', 'age': 18}

  由上面的例子得知,调用一个对象,就会触发对象所在类中的__call__方法的执行,所以我们在调用类Newclass(这里将类也可以看成对象)实例化对象时候,也应该在类Newclass的元类中必然存在一个__call__方法。

class MyMeta(type):
def __init__(self, class_name, class_bases, class_dict):
super().__init__(class_name, class_bases, class_dict) def __call__(self, *args, **kwargs):
print(self) # <class '__main__.Newclass'>
print(args) # ('sgt', 18)
print(kwargs) # {}
return 123 class Newclass(object,metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name) t1 = Newclass('sgt', 18)
print(t1) #

  通过上面例子可以总结出:

  • 调用Newclass就是在调用Newclass类中的__call__方法,(Newclass类中没有按照属性查找去基类中找)
  • 触发__call__方法后会将Newclass传给self,溢出的位置参数传给*,溢出的关键字参数传给**
  • 调用Newclass的返回值就是触发__call__方法函数的返回值,这里通过打印t1得出结果123可以得出。

  好了,我们在来回顾下,实例化对象t1的过程: 

  1. 先产生一个空的对象obj
  2. 调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中
  3. 将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象

  所以,对应的Newclass在实例化出对象t1时候也应该做上面三件事:

class MyMeta(type):
def __init__(self, class_name, class_bases, class_dict):
super().__init__(class_name, class_bases, class_dict) def __call__(self, *args, **kwargs):
# 1 调用__new__产生一个空对象obj:
obj = self.__new__(self) # __new__会产生空对象的名称空间
# 这里self.__new__是通过self(类Newclass)来调用__new__方法,通过属性查找默认在基类object中
# 括号里self的意思是创建类Newclass的对象的名称空间。
# 2 调用__init__初始化空对象obj
self.__init__(obj, *args, **kwargs) # 注意这里的第一个参数是obj,因为我们初始化的是我么创建的空对象
# 3 返回初始化好的对象obj
return obj
class Newclass(object,metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name) t1 = Newclass('sgt', 18) # 实例化对象
print(t1.__dict__) # {'name': 'sgt', 'age': 18} # 查看实例化对象的结果

  上面就是我么通过调用类,实例化对象的过程元类中的__call__做的事情,既然知道了这个过程,我么也能自定义私人的元类,那么我们就可以从基础上改写__call__方法来控制Newclass类调用的过程,比如将Newclass类实例化的对象的属性变成我们想要的结果。

class MyMeta(type):
def __init__(self, class_name, class_bases, class_dict):
super().__init__(class_name, class_bases, class_dict) def __call__(self, *args, **kwargs):
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs) # 在初始化完的对象返回之前进行修改
res = {k.upper(): v for k, v in obj.__dict__.items()} # 将对象的属性名大写
return res class Newclass(object,metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name) t1 = Newclass('sgt', 18)
print(t1) # {'NAME': 'sgt', 'AGE': 18}

4、在知道元类存在的情况下的属性查找顺序

  前面我们在没有学习元类的时候对属性查找的顺序的认识只终止于object,但是今天我们知道了元类的存在那么此时属性查找顺序是什么样的呢?

class MyMeta(type):
n = 444
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj class Bar(object):
n = 333 class Foo(Bar):
n = 222 class Newclass(Foo,metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
n = 111
coutry = 'China'
def task(self):
print('%s is sleeping' % self.name) print(Newclass.n)
# 自下而上依次注释各个类中的n=xxx,然后重新运行程序,
# 发现n的查找顺序为Newclass->Foo->Bar->object->MyMeta->type

  此时,属性查找顺序应该两层,一层是对象层(按照mro顺序查找),第二层类层(元类中查找)

# 查找顺序:
# 1、先对象层:Newclass->Foo->Bar->object
# 2、然后元类层:MyMeta->type

 

面向对象之元类(metaclass)的更多相关文章

  1. Python面向对象之元类(metaclass)

    点进来看就完事了铁汁!      

  2. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  3. (转)元类metaclass

    阅读目录 一 前言 二 什么是元类 三 class关键字创建类的流程分析 五 自定义元类控制类OldboyTeacher的创建 六 自定义元类控制类OldboyTeacher的调用 六 再看属性查找 ...

  4. 深刻理解Python中的元类metaclass(转)

    本文由 伯乐在线 - bigship 翻译 英文出处:stackoverflow 译文:http://blog.jobbole.com/21351/ 译注:这是一篇在Stack overflow上很热 ...

  5. 深刻理解Python中的元类(metaclass)

    译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...

  6. Python中的元类(metaclass)

    推荐+收藏:深刻理解Python中的元类(metaclass) 做一些笔记学习学习: 在大多数编程语言中,类就是用来描述如何生成一个对象的代码段,在Python中类也是一个对象,这个(类)对象自身拥有 ...

  7. [转] 深刻理解Python中的元类(metaclass)

    非常详细的一篇深入讲解Python中metaclass的文章,感谢伯乐在线-bigship翻译及作者,转载收藏. 本文由 伯乐在线 - bigship 翻译.未经许可,禁止转载!英文出处:stacko ...

  8. 深刻理解Python中的元类(metaclass)【转】

    译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...

  9. 深入理解Python中的元类(metaclass)

    原文 译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍 ...

  10. python——深刻理解Python中的元类(metaclass)

    译注:这是一篇在Stack overflow上 很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉 ...

随机推荐

  1. 健康检查NET Core之跨平台的实时性能监控

    ASP.NET Core之跨平台的实时性能监控(2.健康检查)   前言 上篇我们讲了如何使用App Metrics 做一个简单的APM监控,最后提到过健康检查这个东西. 这篇主要就是讲解健康检查的内 ...

  2. 去掉word文档两边的空白

    1.设置-页面布局-页边距,把左边距和右边距的数据设置到最小就好,一般为0.43CM 2.把WORD页面顶部标尺,左右拉到最底,如图: 3.在打印预览里,设置页边距,操作方法同 上述 1,如图:

  3. EOJ Problem #3261 分词 trie + dp + 小剪枝

    http://acm.ecnu.edu.cn/problem/3261/ 分词 Time limit per test: 1.0 seconds Time limit all tests: 1.0 s ...

  4. csrf攻击实例

    CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作.比如说,受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求 ht ...

  5. 传纸条(scrip)

    传纸条(scrip) 题目背景 Awson是某国际学校信竞组的一只菜鸡.每次竞赛考试都只能垫底.终于有一天,他决定不再苟活,发挥他的人脉优势,准备在一次竞赛考试时传纸条作弊. 题目描述 他预先知道了考 ...

  6. There is much opportunity for anyone willing to dedicate himself to his labors.

    There is much opportunity for anyone willing to dedicate himself to his labors.付出努力的人才有机会出人头地.

  7. Android笔记--LayoutInflator源码和使用分析

    LayoutInflator源码分析 获取LayoutInflator对象 获取LayoutInflator的方式有两种: 使用LayoutInflator.from(Context context) ...

  8. Python3+selenium3环境搭建笔记

    系统:win7 64位浏览器:ie9 64位 chrome70 32位 firefox63 64位python版本:3.6.5 Windows x86 executable installersele ...

  9. Literals

    Uppercase or lowercase L means long (however, using a lowercase l is confusing because it can look l ...

  10. 转载 - 浅析我曾遇到的几个便宜VPS服务器

    本文来自:http://www.jianshu.com/p/7d8cfa87fa32 有些时候可能并不是我们工作和业务的需要,但是网上就是这么的邪门,如果看到便宜的衣服不去购买深怕自己吃亏.所以每年的 ...