Python之元类详解

一、引子

元类属于Python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也是自圆其说,点到为止,从队元类的控制上来看就破绽百出,逻辑混乱;

二、什么是元类

一切源自于一句话:Python中一切皆为对象。让我们先定义一个类,然后逐步分析

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class MyTeacher(object):
    school='john'

    def __init__(self,name,age):
        self.name = name
        self.age =age

    def say(self):
        print('%s says welcome to the john to learn Python' %self.name)

所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象t1是调用类MyTeacher得到的

t1 = MyTeacher('zhangsan',18)
print(type(t1))   # 查看对象t1的类是<class '__main__.MyTeacher'>

如果一切皆为对象,那么类MyTeacher本质是一个对象,既然所有的对象都是调用类得到的,那么MyTeacher必然也是调用类一个类得到的,这个类称为元类

于是我们可以推导出--->产生MyTeacher的过程一定发生了:MyTeacher=元类(...)

print(type(MyTeacher))    # 结果为<class 'type'>,证明是调用类type这个元类产生的MyTeacher,即默认的元类为type

三、class关键字创建类的流程分析

上文我们基于Python中一切皆为对象概念分析出:我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以称为类的类),内置的元类为type

class的关键字在帮我们创建类时,必然帮我们调用类元类MyTeacher=type(...),那调用type时传入的参数是什么呢?必然是类的关键组部分,一个类有三大组成部分,分别是:

  • 类名class_name=‘MyTeacher‘
  • 基类们class_bases=(object,)
  • 类的名称空间class_dic,类的名称空间是执行类体代码而得到的

调用type时会依次传入以上三个参数

综合,class关键字帮我们创建一个类应该细分为一下四个过程:

四、exec的用法

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# exec:三个参数
# 参数一:包含一系列Python的字符串
# 参数二:全局作用域(字典形式),如果不指定,默认为globals()
# 参数三:局部作用域(字典形式),如果不指定,默认为locals()
# 可以把exec命令的执行当成一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

g={
    'x':1,
    'y':2
}

l={}

exec(
'''
global x,z
x=100
z=200
m=300
''',g,l)

print(g) #{'x': 100, 'y': 2,'z':200,......}
print(l) #{'m': 300}

五、自定义元类控制类MyTeacher的创建

一个类没有声明自己的元类,默认他的元类时type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用mataclass关键字参数为一个类指定元类

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class Mymeta(type):  # 只有继承类type类才能称之为一个元类,否则就是一个普通自定义类
    pass

class MyTeacher(object,metaclass=Mymeta): # MyTeacher=Mymeta('MyTeacher',(object),{...})
    school = 'john'

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the zhangsan to learn Python' %self.name)

        

自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即MyTeacher=Mymeta(‘MyTeacher‘,(object),{...}),调用Mymeta会先产生一个空对象MyTeacher,然后连同调用Mymeta括号内的参数一同传给Mymeta下的__init__方法,完成初始化,于是我们可以

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __init__(self,class_name,class_bases,class_dic):
        # print(self) #<class '__main__.MyTeacher'>
        # print(class_bases) #(<class 'object'>,)
        # print(class_dic) #{'__module__': '__main__', '__qualname__': 'MyTeacher', 'school': 'My', '__init__': <function MyTeacher.__init__ at 0x102b95ae8>, 'say': <function MyTeacher.say at 0x10621c6a8>}
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)  # 重用父类的功能

        if class_name.islower():
            raise TypeError('类名%s请修改为驼峰体' %class_name)

        if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
            raise TypeError('类中必须有文档注释,并且文档注释不能为空')

class MyTeacher(object,metaclass=Mymeta): # MyTeacher=Mymeta('MyTeacher',(object),{...})
    """
    类MyTeacher的文档注释
    """
    school='My'

    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say(self):
        print('%s says welcome to the My to learn Python' %self.name)

六、自定义元类控制类MyTeacher的调用

储备知识:call

#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Foo:
    def __new__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)

obj = Foo()

# 1. 要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会调用对象时自动触发
# 2. 调用obj的返回值就是__call__方法的返回值
res=obj(1,2,3,x=1,y=2)

由上例得知,调用一个对象,就是触发对象所在类中的__call__方法的执行,如果把MyTeacher也当作一个对象,那么在MyTeacher这个对象的类也必然存在一个__call__方法。

class Mymeta(type): # 只有继承类type类才能称为一个元类,否则就是一个普通的自定义类
    def __call__(self, *args, **kwargs):
        print(self)  # <class '__main__.MyTeacher'>
        print(args)  # ('zhangsan', 18)
        print(kwargs)  # {}
        return 123

class MyTeacher(object,metaclass=Mymeta):
    school = 'john'
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the john to learn Python' %self.name)

# 调用MyTeacher就是在调用Myteacher类中__call__方法
# 然后将MyTeacher传给self,益出的位置参数传给*,益出的关键字参数传给**
# 调用MyTeacher的返回值就是调用__call__的返回值

t1 = MyTeacher('zhangsan',18)
print(t1)  # 123

默认地,调用t1=MyTeacher(‘zhangsan‘,18)会做三件事

  1. 产生一个空对象obj
  2. 调用__init__方法初始化对象obj
  3. 返回初始化好的obj

对应着,MyTeacher类中的__call__方法也应该做这三件事

class Mymeate(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __call__(self, *args, **kwargs):  # self=<class '__main__.MyTeacher'>
        # 1. 调用__new__ 产生类一个空对象obj
        obj = self.__new__(self) # 此处的self是类MyTeacher,必须传参,代表创建一个MyTeacher的对象obj

        # 2. 调用__init__初始化空对象obj
        self.__init__(obj,*args,**kwargs)

        # 3. 返回初始化好的对象obj

        return obj

class MyTeacher(object,metaclass=Mymeate):
    school = 'john'

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the john to learn Python' %self.name)

t1 = MyTeacher('zhangsan',18)
print(t1.__dict__)  # {'name': 'zhangsan', 'age': 18}

上例的__call__相当于一个模版,我们可以在该基础上改写__call__的逻辑从而控制调用MyTeacher的过程。
比如将MyTeacher的对象的所有属性都变成私有的。

class Mymeta(type):  # 只有继承类type类才能称之为一个元类,否则就是一个普通的自定义
    def __call__(self, *args, **kwargs):   # self=<class '__main__.MyTeacher'>
        obj=self.__new__(self)  # 此处的self是类MyTeacher,必须传参,代表创建一个MyTeacher的对象obj

        # 2. 调用__init__初始化空对象obj
        self.__init__(obj,*args,**kwargs)

        # 在初始化之后,obj.__dict__里就有值了

        obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()}

        #3、返回初始化好的对象obj
        return obj

class MyTeacher(object,metaclass=Mymeta):
    school = 'john'

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the john to learn Python' %self.name)

t1=MyTeacher('zhangsan',18)
print(t1.__dict__)   # {'_MyTeacher__name': 'zhangsan', '_MyTeacher__age': 18}

六、再看属性查找

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

其实我们用class自定义的类也全部都是对象(包括object类本身也是元类type的一个实例,可以用type(object)查看),如果把类当成对象来看,将下述继承应该说成是:对象用MyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object

class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    n = 444

    def __call__(self, *args, **kwargs): #self=<class '__main__.MyTeacher'>
        obj=self.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj

class Bar(object):
    n = 333

class Foo(Bar):
    n = 222

class MyTeacher(Foo,metaclass=Mymeta):
    n = 111

    school = 'john'

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the john to learn Python' % self.name)

print(MyTeacher.n)  #自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为MyTeacher->Foo->Bar->object->Mymeta->type

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

原文地址:https://www.cnblogs.com/baishuchao/p/9314111.html

时间: 2024-08-30 07:30:05

Python之元类详解的相关文章

4、Python中的类详解(0601)

类:将同一种具体物事的共同特性抽象出来的表现 状态和转换这些状态的操作 数据: 变量:就是属性 方法: 函数:操作变量引用的数据的代码 面向对象编程的3原则 封装.集成和多态 class class_name 原文地址:https://www.cnblogs.com/hanshanxiaoheshang/p/9374972.html

python之模块datetime详解

# -*- coding: utf-8 -*- #python 27 #xiaodeng #python之模块datetime详解 import datetime #data=datetime.date(2015,11,9)#表示日期的类 #data=datetime.time(hour[,minute[,second[,microsecond[,tzinfo]]]])#表示时间的类,从小时时间开始为参数 #data=datetime.datetime(year,month,day[,hour[

python里的splitlines详解

Python的split方法函数可以分割字符串成列表,默认是以空格作为分隔符sep来分割字符串. In [1]: s = "www jeapedu com" In [2]: print s.split() ['www', 'jeapedu', 'com'] 当然可以改变sep分割字符串为其他字符串. In [6]: t = "www.jeapedu.com" In [7]: print t.split(".") ['www', 'jeapedu'

Python中time模块详解

在Python中,与时间处理有关的模块就包括:time,datetime以及calendar.这篇文章,主要讲解time模块. 在开始之前,首先要说明这几点: 在Python中,通常有这几种方式来表示时间:1)时间戳 2)格式化的时间字符串 3)元组(struct_time)共九个元素.由于Python的time模块实现主要调用C库,所以各个平台可能有所不同. UTC(Coordinated Universal Time,世界协调时)亦即格林威治天文时间,世界标准时间.在中国为UTC+8.DST

Python数据类型及其方法详解

Python数据类型及其方法详解 我们在学习编程语言的时候,都会遇到数据类型,这种看着很基础也不显眼的东西,却是很重要,本文介绍了python的数据类型,并就每种数据类型的方法作出了详细的描述,可供知识回顾. 一.整型和长整型 整型:数据是不包含小数部分的数值型数据,比如我们所说的1.2.3.4.122,其type为"int" 长整型:也是一种数字型数据,但是一般数字很大,其type为"long" 在python2中区分整型和长整型,在32位的机器上,取值范围是-2

QAction类详解:

先贴一段描述:Qt文档原文: Detailed Description The QAction class provides an abstract user interface action that can be inserted into widgets. In applications many common commands can be invoked via menus, toolbar buttons, and keyboard shortcuts. Since the user

Android技术18:Android中Adapter类详解

1.Adapter设计模式 Android中adapter接口有很多种实现,例如,ArrayAdapter,BaseAdapter,CursorAdapter,SimpleAdapter,SimpleCursorAdapter等,他们分别对应不同的数据源.例如,ArrayAdater对应List和数组数据源,而CursorAdapter对应Cursor对象(一般从数据库中获取的记录集).这些Adapter都需要getView方法返回当前列表项显示的View对象.当Model发生改变时,会调用Ba

C++虚基类详解

1.虚基类的作用从上面的介绍可知:如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员.在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如    c1.A::display( ).在一个类中保留间接共同基类的多份同名成员,这种现象是人们不希望出现的.C++提供虚基类(virtual base class )的方法,使得在继承间接共同基类时只保留一份成员.现在,将类A声明为

URLConnection类详解

为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/SJQ. http://www.cnblogs.com/shijiaqi1066/p/3753224.html URLConnection概述 URLConnection是一个抽象类,表示指向URL指定资源的活动连接. URLConnection类本身依赖于Socket类实现网络连接.一般认为,URLConnection类提供了比Socket类更易于使用.更高级的网络连接抽象.但实际上,大多数程序员都会忽略它