python3-元类
原文出处:http://www.cnblogs.com/linhaifeng/articles/8029564.html
exec的使用
- #可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
- g={
- 'x':,
- 'y':
- }
- l={}
- exec('''
- global x,z
- x=
- z=
- m=
- ''',g,l)
- print(g) #{'x': , 'y': ,'z':,......}
- print(l) #{'m': }
引子(类也是对象)
- class Foo:
- pass
- f1=Foo() #f1是通过Foo类实例化的对象
python中一切皆对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例), 因而我们可以将类当做一个对象去使用。
对象的使用:
1、都可以被引用,x=obj
2、都可以当做函数的参数传入
3、都可以当做函数的返回值
4、都可以当做容器类的元素, l = [func, obj, 1]
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?
- print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建
- print(type(Foo)) # 输出:<type 'type'>
什么是元类?
元类是类的类, 是类的模板
元类是用来控制如何创建类的, 正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为
元类的实例化结果是我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例, Foo类是type类的一个实例)
type是python的一个内建元类, 用来直接控制生成类, python中任何class定义的类其实都是type类实例化的对象
创建类的两种方式
方式一:使用class关键字
- class Chinese(object):
- country='China'
- def __init__(self,name,age):
- self.name=name
- self.age=age
- def talk(self):
- print('%s is talking' %self.name)
方式二:就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建
- #准备工作:
- #创建类主要分为三部分
- 类名
- 类的父类
- 类的__dict__
- #类名
- class_name='Chinese'
- #类的父类
- class_bases=(object,)
- #类体
- class_body="""
- country='China'
- def __init__(self,name,age):
- self.name=name
- self.age=age
- def talk(self):
- print('%s is talking' %self.name)
- """
步骤一(先处理类体 --> 名称空间):类定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以实现定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程相似,只是后者将__开头的属性变形),生成类的局部空间名称,即填充字典
- class_dic={}
- exec(class_body,globals(),class_dic)
- print(class_dic)
- #{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
步骤二:调用元类type(也可以自定义)来产生类Chinense
- Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo
- print(Foo)
- print(type(Foo))
- print(isinstance(Foo,type))
- '''
- <class '__main__.Chinese'>
- <class 'type'>
- True
- '''
我们看到,type接收三个参数:
- 第一个参数是字符串'Foo', 表示类名
- 第二个参数是元组(object,),表示所有的父类
- 第三个参数是字典,这里是一个孔子点,表示没有定义属性和方法
补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})
类的几个常用方法
1.__new__方法
__new__方法接受的参数和__init__一样,但__init__实在类对象(创建一个空对象)创建之后调用,而__new__方法正式创建这个空对象的方法。__init__方法里面的self实际都是__new__所创建的空对象,__new__返回值就是已经实例化完的对象(__init__),属于类级别方法
2.__init__方法
__init__通常用于初始化一个实例,控制这个初始化的过程,比如添加一属性,做一些额外的操作,发生在类实例(__new__创建出来的空对象)被创建完之后。它是实例级别的方法。无返回值。
3.__call__方法
构造方法的执行是创建对象出发的,即:对象=类名();而对于__call__方法的执行是实例化的时候‘对象=类名()’触发的。
自定义元类控制(实例化)类的行为
type是所有新式类的类,所有类都可以说是type创建的
object是所有新式类的父类
# 一个类没有定义自己的元类,默认他的元类就是type, 除了使用元类type, 用户也可以通过继承type来自定义元类
- #步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建
- class Mymeta(type): # 继承默认元类的一堆属性
- def __init__(self, class_name, class_bases, class_dic):
- # 实例化类的时候,必须给 元类传入 class_name, class_bases, class_dic三个参数, 我们就可以根据传入的这三个参数来控制类的创建(实例化)
- if '__doc__' not in class_dic or not class_dic.get('__doc__').strip():
- raise TypeError('必须为类指定文档注释')
- if not class_name.istitle():
- raise TypeError('类名首字母必须大写')
- super(Mymeta, self).__init__(class_name, class_bases, class_dic)
- class People(object, metaclass=Mymeta):
- country = 'China'
- def __init__(self, name, age):
- self.name = name
- self.age = age
- def talk(self):
- print('%s is talking' % self.name)
- # People这个类在创建的时候注明了他的元类是Mymeta,也就是我们定义的元类, 所以必须满足我们的控制条件,类名首字母必须大写, 必须要有文档注释切不能为空, 否则报出错误信息。
自定义元类控制类的调用 ---> (实例化)类的实例化对象的行为
#如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用
- class People(object,metaclass=type):
- def __init__(self,name,age):
- self.name=name
- self.age=age
- def __call__(self, *args, **kwargs):
- print(self,args,kwargs)
- # 调用类People,并不会触发__call__
- obj=People('egon',)
- # 调用对象obj(,,,a=,b=,c=),才会出发对象的绑定方法obj.__call__(,,,a=,b=,c=)
- obj(,,,a=,b=,c=) #打印:<__main__.People object at 0x10076dd30> (, , ) {'a': , 'b': , 'c': }
- #总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',)时触发执行,然后返回一个初始化好了的对象obj
开始自定义class Mymeta(type): #继承默认元类的一堆属性
class Mymeta(type): #继承默认元类的一堆属性
def __init__(self,class_name,class_bases,class_dic):
if not class_name.istitle():
raise TypeError('类名首字母必须大写')
super(Mymeta,self).__init__(class_name,class_bases,class_dic)
def __call__(self, *args, **kwargs):
#self=People
print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}
#1、产生空对象obj
obj=object.__new__(self)
#2、调用People下的函数__init__,初始化obj
self.__init__(obj,*args,**kwargs)
#3、返回初始化好了的obj
return obj
- class Chinese(object,metaclass=Mymeta):
'''
中文人的类
'''
country='China'- def __init__(self,namem,age):
self.name=namem
self.age=age- def talk(self):
print('%s is talking' %self.name)- obj=Chinese('egon',age=18) #Chinese.__call__(Chinese,'egon',18)
- print(obj.__dict__)
自定义元类来实现单例模式
实现方式一
- class MySQL:
- __instance=None #__instance=obj1
- def __init__(self):
- self.host='127.0.0.1'
- self.port=
- @classmethod
- def singleton(cls):
- if not cls.__instance:
- obj=cls()
- cls.__instance=obj
- return cls.__instance
- def conn(self):
- pass
- def execute(self):
- pass
- # obj1=MySQL()
- # obj2=MySQL()
- # obj3=MySQL()
- #
- # print(obj1)
- # print(obj2)
- # print(obj3)
- obj1=MySQL.singleton()
- obj2=MySQL.singleton()
- obj3=MySQL.singleton()
- print(obj1 is obj3)
实现方式二:
- class Mymeta(type):
- def __init__(self,class_name,class_bases,class_dic):
- if not class_name.istitle():
- raise TypeError('类名的首字母必须大写')
- if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
- raise TypeError('必须有注释,且注释不能为空')
- super(Mymeta,self).__init__(class_name,class_bases,class_dic)
- self.__instance=None
- def __call__(self, *args, **kwargs): #obj=Chinese('egon',age=)
- if not self.__instance:
- obj=object.__new__(self)
- self.__init__(obj)
- self.__instance=obj
- return self.__instance
- class Mysql(object,metaclass=Mymeta):
- '''
- mysql xxx
- '''
- def __init__(self):
- self.host='127.0.0.1'
- self.port=
- def conn(self):
- pass
- def execute(self):
- pass
- obj1=Mysql()
- obj2=Mysql()
- obj3=Mysql()
- print(obj1 is obj2 is obj3)
再看属性查找
在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object
- class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
- n=
- def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
- obj=self.__new__(self)
- self.__init__(obj,*args,**kwargs)
- return obj
- class Bar(object):
- n=
- class Foo(Bar):
- n=
- class OldboyTeacher(Foo,metaclass=Mymeta):
- n=
- 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
于是属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找
- #查找顺序:
- #、先对象层:OldoyTeacher->Foo->Bar->object
- #、然后元类层:Mymeta->type
依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__的查找
- class Mymeta(type):
- n=
- def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
- obj=self.__new__(self)
- print(self.__new__ is object.__new__) #True
- class Bar(object):
- n=
- # def __new__(cls, *args, **kwargs):
- # print('Bar.__new__')
- class Foo(Bar):
- n=
- # def __new__(cls, *args, **kwargs):
- # print('Foo.__new__')
- class OldboyTeacher(Foo,metaclass=Mymeta):
- n=
- 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('egon',) #触发OldboyTeacher的类中的__call__方法的执行,进而执行self.__new__开始查找
总结,Mymeta下的__call__里的self.__new__在OldboyTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__
我们在元类的__call__中也可以用object.__new__(self)去造对象
但我们还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索三个类OldboyTeacher->Foo->Bar,而object.__new__则是直接跨过了他们三个
python3-元类的更多相关文章
- python3 元类编程的一个例子
[引子] 虽然我们可以通过“class”语句来定义“类”,但是要想更加细粒度的控制“类”的创建,要使用元类编程才能实现. 比如说我们要实现这样的一个约束.所有项目中用到的类都应该要为它定义的方法提供文 ...
- Python3 面向对象之-----元类
元类 1. 类也是对象 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍然成立: >>> class ObjectCreator(object ...
- python3全栈开发-内置函数补充,反射,元类,__str__,__del__,exec,type,__call__方法
一.内置函数补充 1.isinstance(obj,cls)检查是否obj是否是类 cls 的对象 class Foo(object): pass obj = Foo() print(isinstan ...
- python3全栈开发- 元类metaclass(面试必考题)
一.知识储备 #exec:三个参数 #参数一:字符串形式的命令 #参数二:全局作用域(字典形式),如果不指定,默认为globals() #参数三:局部作用域(字典形式),如果不指定,默认为locals ...
- 元类理解与元类编程 《Python3网络爬虫开发》中第九章代理的使用代码Crawler中代码的理解
__new__与__init__的理解 __new__()方法是在创建实例之前被调用的,它的作用是创建一个实例,然后返回该实例对象,它是一个静态方法. __init__() 当实例被创建完成之后被调用 ...
- Python中的元类和__metaclass__
1.什么是元类 元类让你来定义某些类是如何被创建的,从根本上说,赋予你如何创建类的控制权.可以把元类想成是一个类中类,或是一个类,它的实例是其它的类.当某个类调用type()函数时,你就会看到它到底是 ...
- 5、flask之信号和mateclass元类
本篇导航: flask实例化参数 信号 metaclass元类解析 一.flask实例化参数 instance_path和instance_relative_config是配合来用的:这两个参数是用来 ...
- 【python进阶】详解元类及其应用2
前言 在上一篇文章[python进阶]详解元类及其应用1中,我们提到了关于元类的一些前置知识,介绍了类对象,动态创建类,使用type创建类,这一节我们将继续接着上文来讲~~~ 5.使⽤type创建带有 ...
- Python进阶开发之元类编程
系列文章 √第一章 元类编程,已完成 ; 本文目录 类是如何产生的如何使用type创建类理解什么是元类使用元类的意义元类实战:ORM . 类是如何产生的 类是如何产生?这个问题肯定很傻.实则不然,很多 ...
- python 面向对象进阶之元类metaclass
一:知识储备 exec exec:三个参数 参数一:字符串形式的命令 参数二:全局作用域(字典形式),如果不指定,默认为globals() 参数三:局部作用域(字典形式),如果不指定,默认为local ...
随机推荐
- spring boot 返回json字符串 null值转空字符串
@Configuration public class JacksonConfig { @Bean @Primary @ConditionalOnMissingBean(ObjectMapper.cl ...
- 2017-12-14python全栈9期第一天第八节之循环语句while
12,while. while 条件: 循环体 无限循环. 终止循环:1,改变条件,使其不成立. 2,break continue
- MyBatis-注解方式整合SSM
Spring.Spring MVC.MyBatis 整合 一.依赖 <?xml version="1.0" encoding="UTF-8"?> & ...
- HDU - 3974 Assign the task (线段树区间修改+构建模型)
https://cn.vjudge.net/problem/HDU-3974 题意 有一棵树,给一个结点分配任务时,其子树的所有结点都能接受到此任务.有两个操作,C x表示查询x结点此时任务编号,T ...
- HDU 1016(素数环 深搜)
题意是说对一个长度为 n 的数环进行排列,使得相邻两数的和为素数,按从小到大的顺序依次输出. 因为是环,所以总能调整成以 1 为序列首输出.用深度优先搜索的方法即可.在判断素数时由于 n 小于 20, ...
- multiprocessing.Manager共享内存的问题记录
问题:https://stackoverflow.com/questions/8640367/python-manager-dict-in-multiprocessing 使用 multiproces ...
- forEach 如何提前终止 跳出运行
forEach 如何提前终止 跳出运行 try{ arr.forEach(function(item,index){ if (...) { foreach.break=new Error(" ...
- Linux文件权限设置
基本概念 https://linux.cn/article-7418-1.html#3_8880 用户管理 文件权限设置 -添加用户账户08% -理解 /etc/passwd 中的内容12% -理解 ...
- Javaweb学习笔记——(二十)——————Javaweb监听器、国际化
Javaweb监听器 三大组件 *Servlet *Listener *Filter Listener:监听器 1.初次相见:A ...
- 七、文件IO——I/O处理方式和文件锁
7.1 I/O 处理方式 7.1.1 I/O处理的五种模型 阻塞I/O模型 若所调用的 I/O 函数没有完成相关的功能就会使进程挂起,直到相关数据到达才会返回.如 终端.网络设备的访问. 非阻塞模型 ...