面向对象之元类(metaclass)
一、前言:
要搞懂元类必须要搞清楚下面几件事:
类创建的时候,内部过程是什么样的,也就是我们定义类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的过程:
- 先产生一个空的对象obj
- 调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中
- 将初始化的对象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)的更多相关文章
- Python面向对象之元类(metaclass)
点进来看就完事了铁汁!
- [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式
使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...
- (转)元类metaclass
阅读目录 一 前言 二 什么是元类 三 class关键字创建类的流程分析 五 自定义元类控制类OldboyTeacher的创建 六 自定义元类控制类OldboyTeacher的调用 六 再看属性查找 ...
- 深刻理解Python中的元类metaclass(转)
本文由 伯乐在线 - bigship 翻译 英文出处:stackoverflow 译文:http://blog.jobbole.com/21351/ 译注:这是一篇在Stack overflow上很热 ...
- 深刻理解Python中的元类(metaclass)
译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...
- Python中的元类(metaclass)
推荐+收藏:深刻理解Python中的元类(metaclass) 做一些笔记学习学习: 在大多数编程语言中,类就是用来描述如何生成一个对象的代码段,在Python中类也是一个对象,这个(类)对象自身拥有 ...
- [转] 深刻理解Python中的元类(metaclass)
非常详细的一篇深入讲解Python中metaclass的文章,感谢伯乐在线-bigship翻译及作者,转载收藏. 本文由 伯乐在线 - bigship 翻译.未经许可,禁止转载!英文出处:stacko ...
- 深刻理解Python中的元类(metaclass)【转】
译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...
- 深入理解Python中的元类(metaclass)
原文 译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍 ...
- python——深刻理解Python中的元类(metaclass)
译注:这是一篇在Stack overflow上 很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉 ...
随机推荐
- Codeforces Round #527-D1. Great Vova Wall (Version 1)(思维+栈)
time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...
- HTTP/2之旅 (翻译)
Journey to HTTP/2 HTTP/2 距离我上一次通过博客写作以来, 经过了很长的一段安静的时间. 因为一直没有足够的时间投入其中. 直到现在有了一些空闲的时间, 我想利用他们写一些HTT ...
- Spring boot中应用jpa jpa用法
https://blog.csdn.net/u012582402/article/details/78717705
- NET Everywhere
NET Everywhere 8月份已经发布了.NET Core 2.0, 大会Keynote 一开始花了大量的篇幅回顾.NET Core 2.0的发布,社区的参与度已经非常高.大会的主题是.NET ...
- js动态更换img的src问题
在本地开发测试过程中,通过js动态更换img的src没有问题,图片正常切换,但是放在服务器上后测试发现,图片不显示,解决方法为:在对应onclick事件执行切换图片的js函数后加上一个return f ...
- JVM垃圾回收机制四
GCRoots与可达性分析 Java中的四种引用 强引用.软引用.弱引用.虚引用.这四种引用的强度是逐渐减弱的,JVM垃圾回收的力度是逐渐增强的. 四种引用的作用 1.可以让程序员通过代码来控制对象的 ...
- xcode在代码中查找中文
总是忘记xcode中查找中文,这次记下来,以后就不会忘记了,哈哈 请看下图: 切换到查找,点击find后面的text,选择Regular Expression,然后输入 1. 查找非ascii的字符 ...
- mysql主从设置windows
MySQL 主从复制是其最重要的功能之一.主从复制是一台服务器充当主服务器,另一台或多台服务器充当从服务器,主机自动复制到从机.对于多级复制,数据服务器即可充当主机,也可充当从机.MySQL 复制的基 ...
- pc端常见布局---垂直居中布局 单元素定高
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- LibreOJ #2003. 「SDOI2017」新生舞会
内存限制:256 MiB 时间限制:1500 ms 标准输入输出 题目类型:传统 评测方式:文本比较 上传者: 匿名 01分数规划(并不知道这是啥..) km或费用流(并不会)验证 屠龙宝刀点击就送 ...