元类

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

什么是元类

二、什么是元类

  • 在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. Codeforces 385C Bear and Prime Numbers(素数预处理)

    Codeforces 385C Bear and Prime Numbers 其实不是多值得记录的一道题,通过快速打素数表,再做前缀和的预处理,使查询的复杂度变为O(1). 但是,我在统计数组中元素出 ...

  2. 「CTSC 2008」祭祀

    题目链接 戳我 \(Solution\) 第一问 这道题要知道一个叫做\(Dilworth\)的定理 最长反链\(=\)最小链覆盖 证明(\(from\ r\_64\)): 所以我们只要求一个最小链覆 ...

  3. js上传图片获取原始宽高

    以vue上传图片为例: <template> <div> <input type="file" @change="uploadFile($e ...

  4. 8.1 HTML基础知识点

    8.1 HTML基础知识点 一.HTML是什么? Hyper Text Markup Language 超文本标记语言 的缩写 开发一个html文件,可以有很多方式 :比如Dreamweaver,HB ...

  5. 使用Grizzy+Jersey搭建一个RESTful框架()报错Exception in thread "main" java.lang.AbstractMethodError: javax.ws.rs.core.UriBuilder.uri(Ljava/lang/String;)Ljavax/ws/rs/core/UriBuilder;

    报错的类涉及UriBuilder,我搜索类发现, 这个类存在于两个包中,我在baidu的时候,也有人提到是jar包冲突,我就删除了 这个依赖,问题解决了. 环境搭建过程请见地址https://blog ...

  6. Java企业版文档地址

    地址:http://docs.oracle.com/javaee/7/index.html

  7. VLC-Qt 入门指南

      关于 VLC-Qt VLC-Qt:一个结合了 Qt 应用程序和 libVLC 的免费开源库.它包含了用于媒体播放的核心类,以及用于更快速地进行媒体播放器开发的一些 GUI 类. 官网地址:http ...

  8. weblogic性能调优

    1.设置java参数: a) 编辑Weblogic Server启动脚本文件: /user_projects/domains/Domain_jgbs/bin/startWebLogic.sh /use ...

  9. GitHub:Oracle

    ylbtech-GitHub:Oracle 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部 1. https://github.com/oracle 2.   6 ...

  10. java二进制工具

    可以运用jdk工具监控java应用性能,再配合 jmeter 进行了一个长时间的加压,在加压过程中重点关注了系统资源的使用情况 D:\Program Files (x86)\Java\jdk1.8.0 ...