元类

警告!警告!前方核能来袭!!!想要上车的小伙伴赶紧上车了,时间不多了。。。列车长注意把各车门给我焊死了!请各位小伙伴在路途中勿随意跳车,跳车不规范,亲人两行泪。。。各位乘客我们开始出发了,路途中请注意生命安全,如未及时写下遗书,本列车概不负责!

什么是元类

二、什么是元类

  • 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类
class Foo:  # Foo=元类()
pass

为什么用元类

元类是负责产生类的,所以我们学习元类或自定义类的目的:是为了控制类的产生过程,还可以控制对象的产生过程

内置函数exec

cmd='''
x=1
print('exec函数在此')
def func(self):
pass
'''
class_dic={}
exec(cmd,{},class_dic)
print(class_dic)

该内置方法的作用是把cmd字符串中的代码以键对值的形式放到字典中

class创建类

  • 如果说类也是对象,那么用class关键字的去创建类的过程也是一个实例化的过程,该实例化的目的是为了得到一个类,调用的是元类
  • 用class关键字创建一个类,用的默认的元类type,因此以前说不要用type作为类别判断
class People:  # People=type(...)
country = 'China' def __init__(self, name, age):
self.name = name
self.age = age def eat(self):
print('%s is eating' % self.name)
print(type(People))
<class 'type'>

type实现

cmd='''
x=1
print('exec函数在此')
def func(self):
pass
'''
class_dic={}
exec(cmd,{},class_dic)
People=type('People',(object,),class_dic)
print(People__name__)

自定义元类

class yuan(type):
def __init__(self,name,bases,dic):
if name.startswith('sb'):
raise Exception('不能以sb开头')
print(name)
class sbPerson(object,metaclass=yuan):
def __init__(self,name,age):
self.name=name
self.age=age
def print_age(self):
print(self.age) p=sbPerson('nick',19)
  • 使用自定义的元类
class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __init__(self, class_name, class_bases, class_dic):
print('self:', self) # 现在是People
print('class_name:', class_name)
print('class_bases:', class_bases)
print('class_dic:', class_dic)
super(Mymeta, self).__init__(class_name, class_bases,
class_dic) # 重用父类type的功能
  • 分析用class自定义类的运行原理(而非元类的的运行原理):

    1. 拿到一个字符串格式的类名class_name='People'
    2. 拿到一个类的基类们class_bases=(obejct,)
    3. 执行类体代码,拿到一个类的名称空间class_dic={...}
    4. 调用People=type(class_name,class_bases,class_dic)
class People(object, metaclass=Mymeta):  # People=Mymeta(类名,基类们,类的名称空间)
country = 'China' def __init__(self, name, age):
self.name = name
self.age = age def eat(self):
print('%s is eating' % self.name)
self: <class '__main__.People'>
class_name: People
class_bases: (<class 'object'>,)
class_dic: {'__module__': '__main__', '__qualname__': 'People', 'country': 'China', '__init__': <function People.__init__ at 0x10a0bcbf8>, 'eat': <function People.eat at 0x10a0bc2f0>}
  • 自定义元类控制类的产生过程,类的产生过程其实就是元类的调用过程
  • 我们可以控制类必须有文档,可以使用如下的方式实现
class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __init__(self, class_name, class_bases, class_dic):
if class_dic.get('__doc__') is None or len(
class_dic.get('__doc__').strip()) == 0:
raise TypeError('类中必须有文档注释,并且文档注释不能为空')
if not class_name.istitle():
raise TypeError('类名首字母必须大写')
super(Mymeta, self).__init__(class_name, class_bases,
class_dic) # 重用父类的功能
try: class People(object, metaclass=Mymeta
): #People = Mymeta('People',(object,),{....})
# """这是People类"""
country = 'China' def __init__(self, name, age):
self.name = name
self.age = age def eat(self):
print('%s is eating' % self.name)
except Exception as e:
print(e)
类中必须有文档注释,并且文档注释不能为空

_ _ call _ _

  • 要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法、、__call__方法,该方法会在调用对象时自动触发
class Foo:
def __call__(self, *args, **kwargs):
print(args)
print(kwargs)
print('__call__实现了,实例化对象可以加括号调用了') obj = Foo()
obj('nick', age=18)
('nick',)
{'age': 18}
__call__实现了,实例化对象可以加括号调用了

_ _ new _ _

我们之前说类实例化第一个调用的是__init__,但__init__其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 new 方法。

__new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

注意:new() 函数只能用于从object继承的新式类。

class A:
pass class B(A):
def __new__(cls):
print("__new__方法被执行")
return cls.__new__(cls) def __init__(self):
print("__init__方法被执行") b = B()

自定义元类控制的实例化

class Mymeta(type):
def __call__(self, *args, **kwargs):
print(self) # self是People
print(args) # args = ('nick',)
print(kwargs) # kwargs = {'age':18}
# return 123
# 1. 先造出一个People的空对象,申请内存空间
# __new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。
obj = self.__new__(self) # 虽然和下面同样是People,但是People没有,找到的__new__是父类的
# 2. 为该对空对象初始化独有的属性
self.__init__(obj, *args, **kwargs)
# 3. 返回一个初始化好的对象
return obj
  • People = Mymeta(),People()则会触发__call__
class People(object, metaclass=Mymeta):
country = 'China' def __init__(self, name, age):
self.name = name
self.age = age def eat(self):
print('%s is eating' % self.name) # 在调用Mymeta的__call__的时候,首先会找自己(如下函数)的,自己的没有才会找父类的
# def __new__(cls, *args, **kwargs):
# # print(cls) # cls是People
# # cls.__new__(cls) # 错误,无限死循环,自己找自己的,会无限递归
# obj = super(People, cls).__new__(cls) # 使用父类的,则是去父类中找__new__
# return obj
  • 类的调用,即类实例化就是元类的调用过程,可以通过元类Mymeta的__call__方法控制
  • 分析:调用Pepole的目的
    1. 先造出一个People的空对象
    2. 为该对空对象初始化独有的属性
    3. 返回一个初始化好的对象
obj = People('nick', age=18)
<class '__main__.People'>
('nick',)
{'age': 18}
print(obj.__dict__)
{'name': 'nick', 'age': 18}

属性查找顺序

结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???

在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object

class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n = 444 def __call__(self, *args,
**kwargs): #self=<class '__main__.OldboyTeacher'>
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj class Bar(object):
n = 333 class Foo(Bar):
n = 222 class OldboyTeacher(Foo, metaclass=Mymeta):
n = 111 school = 'oldboy' def __init__(self, name, age):
self.name = name
self.age = age def say(self):
print('%s says welcome to the oldboy to learn Python' % self.name) print(
OldboyTeacher.n
) # 自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type
111
print(OldboyTeacher.n)
111
  • 查找顺序:

    1. 先对象层:OldoyTeacher->Foo->Bar->object
    2. 然后元类层:Mymeta->type

依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__的查找

class Mymeta(type):
n = 444 def __call__(self, *args,
**kwargs): #self=<class '__main__.OldboyTeacher'>
obj = self.__new__(self)
print(self.__new__ is object.__new__) #True class Bar(object):
n = 333 # def __new__(cls, *args, **kwargs):
# print('Bar.__new__') class Foo(Bar):
n = 222 # def __new__(cls, *args, **kwargs):
# print('Foo.__new__') class OldboyTeacher(Foo, metaclass=Mymeta):
n = 111 school = 'oldboy' def __init__(self, name, age):
self.name = name
self.age = age def say(self):
print('%s says welcome to the oldboy to learn Python' % self.name) # def __new__(cls, *args, **kwargs):
# print('OldboyTeacher.__new__') OldboyTeacher('nick',
18) # 触发OldboyTeacher的类中的__call__方法的执行,进而执行self.__new__开始查找

总结,Mymeta下的__call__里的self.new__在OldboyTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__

python-day29(正式学习)的更多相关文章

  1. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  2. Requests:Python HTTP Module学习笔记(一)(转)

    Requests:Python HTTP Module学习笔记(一) 在学习用python写爬虫的时候用到了Requests这个Http网络库,这个库简单好用并且功能强大,完全可以代替python的标 ...

  3. 从Theano到Lasagne:基于Python的深度学习的框架和库

    从Theano到Lasagne:基于Python的深度学习的框架和库 摘要:最近,深度神经网络以“Deep Dreams”形式在网站中如雨后春笋般出现,或是像谷歌研究原创论文中描述的那样:Incept ...

  4. Comprehensive learning path – Data Science in Python深入学习路径-使用python数据中学习

    http://blog.csdn.net/pipisorry/article/details/44245575 关于怎么学习python,并将python用于数据科学.数据分析.机器学习中的一篇非常好 ...

  5. (转载)Python装饰器学习

    转载出处:http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html 这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方 ...

  6. 正式学习React(五) react-redux源码分析

    磨刀不误砍柴工,咱先把react-redux里的工具函数分析一下: 源码点这里  shallowEqual.js export default function shallowEqual(objA, ...

  7. 正式学习React(一) 开始学习之前必读

    为什么要加这个必读!因为webpack本身是基于node环境的, 里面会涉及很多路径问题,我们可能对paths怎么写!webpack又是怎么找到这些paths的很迷惑. 本文是我已经写完正式学习Rea ...

  8. python网络爬虫学习笔记

    python网络爬虫学习笔记 By 钟桓 9月 4 2014 更新日期:9月 4 2014 文章文件夹 1. 介绍: 2. 从简单语句中開始: 3. 传送数据给server 4. HTTP头-描写叙述 ...

  9. Python装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 ? 1 2 3 4 5 6 7 8 # -*- ...

  10. Python的基础学习(第二周)

    模块初始 sys模块 import sys sys.path #打印环境变量 sys.argv#打印该文件路径 #注意:该文件名字不能跟导入模块名字相同 os模块 import os cmd_res ...

随机推荐

  1. 2018-2019-2 20165215《网络对抗技术》Exp10 Final Windows本地内核提权+Exploit-Exercises Nebula学习与实践

    目录 PART ONE :Windows本地内核提权 漏洞概述 漏洞原理 漏洞复现 windbg调试本地内核 查看SSDT表和SSDTShadow表 查看窗口站结构体信息 利用Poc验证漏洞 漏洞利用 ...

  2. 【软件工程】团队Git现场编程实战

    组长博客链接 博客链接 组员职责分工 队员 职责分工 恩泽 进行任务的划分与安排,调用API,负责餐饮商铺及商圈信息的获取 金海 解析API返回的json数据,提取有关信息 君曦 部分算法编写 季城 ...

  3. Forcepoint

    Forcepoint One Endpoint Diagnostics Tool C:\Program Files\Websense\Websense Endpoint\WEPDiag.exe &qu ...

  4. apt 软件安装问题

    1.sudo apt-get update由于没有公钥,无法验证下列签名: NO_PUBKEY F42ED6FBAB17C654 解决方法:安装公钥 sudo apt-key adv --keyser ...

  5. [String.Format(转换时间格式)]

    string.Format("{0:d}", System.DateTime.Now);   // 2017/6/2; string.Format("{0:D}" ...

  6. vmalloc详解

    vmalloc是一个接口函数, 内核代码使用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存. 只需要一个参数,以字节为单位. 使用vmalloc的最著名的实例是内核对模块的实现. 因为模块可能 ...

  7. OGG 从Oracle备库同步数据至kafka

    OGG 从Oracle备库同步数据至kafka Table of Contents 1. 目的 2. 环境及规划 3. 安装配置JDK 3.1. 安装jdk 3.2. 配置环境变量 4. 安装Data ...

  8. linux配置信息收集

    CPU型号:# cat /proc/cpuinfo |grep "model name"# cat /proc/cpuinfo | grep name | cut -f2 -d: ...

  9. Eureka 2.0 闭源--选择Consul???[转]

    原文链接: https://www.cnblogs.com/williamjie/p/9369800.html 在上个月我们知道 Eureka 2.0 闭源了,但其实对国内的用户影响甚小,一方面国内大 ...

  10. 模拟SQLserver IO压力测试 工具编 SQLIOSIM

    描述 最近有业务需求需了解客户的服务器SQLserver 的IO情况,而不仅仅是通过系统计数器 了解硬盘的IO情况或者使用CrystalDiskMark或者Trace重播进行压力测试 .这时SQL S ...