原文出处: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-元类的更多相关文章

  1. python3 元类编程的一个例子

    [引子] 虽然我们可以通过“class”语句来定义“类”,但是要想更加细粒度的控制“类”的创建,要使用元类编程才能实现. 比如说我们要实现这样的一个约束.所有项目中用到的类都应该要为它定义的方法提供文 ...

  2. Python3 面向对象之-----元类

    元类 1. 类也是对象 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍然成立: >>> class ObjectCreator(object ...

  3. python3全栈开发-内置函数补充,反射,元类,__str__,__del__,exec,type,__call__方法

    一.内置函数补充 1.isinstance(obj,cls)检查是否obj是否是类 cls 的对象 class Foo(object): pass obj = Foo() print(isinstan ...

  4. python3全栈开发- 元类metaclass(面试必考题)

    一.知识储备 #exec:三个参数 #参数一:字符串形式的命令 #参数二:全局作用域(字典形式),如果不指定,默认为globals() #参数三:局部作用域(字典形式),如果不指定,默认为locals ...

  5. 元类理解与元类编程 《Python3网络爬虫开发》中第九章代理的使用代码Crawler中代码的理解

    __new__与__init__的理解 __new__()方法是在创建实例之前被调用的,它的作用是创建一个实例,然后返回该实例对象,它是一个静态方法. __init__() 当实例被创建完成之后被调用 ...

  6. Python中的元类和__metaclass__

    1.什么是元类 元类让你来定义某些类是如何被创建的,从根本上说,赋予你如何创建类的控制权.可以把元类想成是一个类中类,或是一个类,它的实例是其它的类.当某个类调用type()函数时,你就会看到它到底是 ...

  7. 5、flask之信号和mateclass元类

    本篇导航: flask实例化参数 信号 metaclass元类解析 一.flask实例化参数 instance_path和instance_relative_config是配合来用的:这两个参数是用来 ...

  8. 【python进阶】详解元类及其应用2

    前言 在上一篇文章[python进阶]详解元类及其应用1中,我们提到了关于元类的一些前置知识,介绍了类对象,动态创建类,使用type创建类,这一节我们将继续接着上文来讲~~~ 5.使⽤type创建带有 ...

  9. Python进阶开发之元类编程

    系列文章 √第一章 元类编程,已完成 ; 本文目录 类是如何产生的如何使用type创建类理解什么是元类使用元类的意义元类实战:ORM . 类是如何产生的 类是如何产生?这个问题肯定很傻.实则不然,很多 ...

  10. python 面向对象进阶之元类metaclass

    一:知识储备 exec exec:三个参数 参数一:字符串形式的命令 参数二:全局作用域(字典形式),如果不指定,默认为globals() 参数三:局部作用域(字典形式),如果不指定,默认为local ...

随机推荐

  1. varStatus="vs"

    通过在百度上查看,发现是属于JSTL 标签.我具体说一下用途吧 就是删除/新增的时候,序号总是连续的从1开始排列,序号的数据与数据库没有关系,序号并不等于id,在数据库中的id仍然是原来的那样,不连续 ...

  2. I/O模型之二:Linux IO模式及 select、poll、epoll详解

    目录: <I/O模型之一:Unix的五种I/O模型> <I/O模型之二:Linux IO模式及 select.poll.epoll详解> <I/O模型之三:两种高性能 I ...

  3. java-Array数组常用操作例子(基础必备)

    package com.net.xinfang.reflect; import java.util.ArrayList; import java.util.Arrays; import java.ut ...

  4. Junit4学习与使用【转】

    参考: http://blog.csdn.net/qqhjqs/article/details/42219037

  5. python --github 刷题

    第 0001 题:做为 Apple Store App 独立开发者,你要搞限时促销,为你的应用生成激活码(或者优惠券),使用 Python 如何生成 200 个激活码(或者优惠券)? import r ...

  6. VS2019预览版发布了

     VS2019正式版已发布:https://www.cnblogs.com/zhaogaojian/p/10648904.html 1.点击下载https://visualstudio.microso ...

  7. nginx静态资源反向代理

    server { listen 80; server_name music.didi365.com; index index.php index.html index.htm; root /alida ...

  8. 十七、文件和目录——minishell(1)

    主函数运行要去读取从标准输入或终端上输入的整个命令行,然后再去解析命令行参数,解析出来之后,要将其封装成一个 program,然后再将 program 放入 job 中,然后再去执行 job 中的命令 ...

  9. 新闻类App顶部菜单栏封装

    概述 最近有一个需求,类似今日头条顶部的菜单栏.唯一区别是需要带可移动的下划线.网上查找资料,发现解决方案大部分是用UIScrollView实现.下方VC控制用UICollectionView.这样可 ...

  10. Docker 容器启动 查看容器状态 - 四

    1.容器两种方式进行启动 一种是基于创建一个容器并启动 docker create docker start 另一种 使用 run 创建自动启动:是状态下的停止 启动 docker start ngi ...