最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

python一切皆对象- 元类详解

XAMPP案例 admin 498浏览 0评论
0Python
python一切皆对象,class关键字定义的类也属于对象,对象是由类实例化得到的,那么类是怎么来的?一起来康康吧~
元类介绍
1
所有的对象都是实例化或者说是通过调用类而得到的,python中一切皆对象,通过class关键字定义的类本质也是对象,对象又是通过调用类得到的,因此通过class关键字定义的类肯定也是调用了一个类得到的,这个类就是元类。元类就是用来实例化产生类的类,因此我们可以得到如下的关系:如何查看python内置的元类呢?可以通过type方法:

>>> class Test():
...     pass
...
>>> type(Test)  # 查看自定义类的类型
<class 'type'>
>>> type(int)  # 查看python内置类型的类型
<class 'type'>

上述代码结果都为<class ‘type’>,type就是内置的元类,class关键字定义的所有的类以及内置的类都是由元类type实例化产生。

type & object
2
由于在python3中没有经典类和新式类的区别,因此python3中object是所有类的基类,那内置元类type又是什么?type是object类的类型,总结来说就是type是object的类型,同时,object又是type的基类,这句话看起来就有问题,到底是先有type还是先有object呢?这个问题有点类似于先有鸡还是先有蛋,我们可以通过代码简单分析一下(因为小庄水平有限…):

>>> object.__class__
<class 'type'>
>>> type.__class__
<class 'type'>

>>> object.__bases__
()>>> type.__bases__
(<class 'object'>,)

>>> type(object)
<class 'type'>
>>> type(type)
<class 'type'>

在python3中类和类型是同一种东西,因此 对象.__class__ 和type(对象)得到的结果是一致的,object的基类为空,但是type的基类为object,但是object的类型又是type。

具体也是挺复杂的,简单来说,object和type是python中的两个源对象,如果有兴趣可以去python官方文档中找到答案。

class关键字创建类的步骤
3
class关键创建类时,一定调用了元类,调用元类type又需要传入什么参数呢?就是类的三大组成部分,分别是:1、类名,比如class_name = ‘Test’2、类的父类(基类),比如class_bases = (object, )3、类的名称空间class_dict,类的名称空间是执行类体代码时得到的在调用type时会依次传入以上三个参数。这里需要补充一个知识的使用就是exec,可以将exec命令作为python的函数执行,可以接收三个参数,分别是:

参数一,包含一系列符合python语法代码的字符串;

参数二,字典形式的全局名称空间中的名字及所对应的值;

参数三,字典形式的局部名称空间中的名字及所对应的值;

# python字符串strs = '''global name, age  # 全局名称
name = 'python'age = 18
addr = 'xx'  # 局部名称'''
# 定义全局作用域中的名字和值
globals = {
   'a': 1,
   'b': 2
}

# 定义局部作用域中的名字和值
locals = {
    'x': 3,
    'y':4
}

exec(strs, globals, locals)

>>> globals
{'a': 1, 'b': 2, ..., 'name': 'python', 'age': 18}

>>> locals
{'x': 3, 'y': 4, 'addr': 'xx'}

了解了exec的作用之后,就可以分析class关键字如何借助type元类产生类的步骤:

# 1 定义类名
class_name = 'Test'

# 2 定义类的基类(父类)
class_bases = (object,)

# 3 执行类体代码拿到类的名称空间
class_dic = {}

# 4 定义类体代码(本质是字符串)
class_body = """
def __init__(self,name,age):
    self.name=name
    self.age=age

def test(self):
    print('%s:%s' %(self.name,self.name))
"""

# 5 将字符串转为python能识别的语法:将class_body运行时产生的名字存入class_dic中
exec(class_body,{},class_dic)
# 查看类的名称空间
print(class_dic)

# 6 调用元类产生类
Test = type(class_name, class_bases, class_dic)

# 7 调用类产生对象
t = Test('python', '12')

我们可以通过控制调用元类的步骤控制元类的产生,元类的调用与自定义的普通类的调用步骤相同,所以,我们先来看一下一个类被调用的过程到底发生了什么事情吧!

# 定义一个类
class Test():
    
    def __init__(self):
        self.name = 'python'
        
    def test(self):
        print(self.name)
        
# 类实例化产生对象
t = Test()

基于之前的知识,我们知道类实例化产生对象时,会自动调用类中的__init__方法,为空对象增加独有属性,问题来了,这个空对象是从哪来的呢?所以说,在调用__init__方法之前肯定还调用了其他方法。

在python3中没有经典类和新式类之分,python3中自定义的普通类都默认继承了object类,所以在调用类产生对象时,在__init__方法运行之前的方法生成了一个空对象并返回,将这个空对象作为参数传给__init__方法,这个方法就是__new__方法。

在类加括号实例化对象时,首先会执行__new__方法,该方法早于__init__方法运行,返回一个空对象,如果该方法没有返回值,就不会调用__init__方法。

知道了类调用的步骤,就可以自定义元类来控制类的产生了。

自定义元类
一个类没有指定自己的元类,默认这个类的元类就是type,除了使用内置元类type,我们可以通过继承type类从而自定义元类,通过在创建普通类时将metaclass关键字指定为自定义元类的名字来为普通类指定元类。

# 自定义元类
class MyMeta(type):  # 自定义元类必须继承type,否则就是普通的类
    
    '''
    早于__init__方法执行,必须返回空对象,由于该方法是调用类后第一个运行的方法,此时并没有对象产生,
因此该方法的第一个参数必须是类本身(MyMeta),*args, **kwargs用来接收调用元类产生对象所需的参数(类名 类的基类 名称空间)
    '''    def __new__(cls,*args, **kwargs):
        return type.__new__(cls, *args, **kwargs)  # 直接调用父类type中的__new__方法
    
    '''
    通过__init__控制类的产生,调用自定义元类与调用内置元类type的方式相同,需要传入类名、类的父类们、类体代码的名称空间,__init__方法中第一个参数来自于__new__方法产生的空对象。
    '''
    def __init(self, class_name, class_bases, class_dict):
        
        '''
        在该方法内可以控制类的产生
        '''
        if not class_name.istitle():  # 实现类名首字母必须大写,否则抛出异常
      raise NameError('类名的首字母必须大写')
        
        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip())==0:  # 实现类必须有文档注释,否则抛出异常
            raise TypeError('必须有文档注释')
自定义元类控制类的创建
首先,不使用class关键字创建类请参照class关键字创建类的原理;然后,来看一下使用自定义元类控制class关键字创建类,大家可以尝试不加文档注释以及类名首字母不大写来验证元类如何控制类的产生:

class Test(metaclass=MyMeta):

    '''
    我是文档注释
    '''

    def __init__(self):
        self.name = 'python'

    def test(self):
        print('test')

从上述代码我们可以得到如下结论:

只要调用类,就会依次调用类中的__new__方法和__init__方法;

__new__方法返回一个空对象,就类似一张白纸;

__init__获取__new__方法中产生的白纸在上面画不同的图案。

 

自定义元类控制类的调用
4
为什么类加括号就可以被调用呢?类调用之后又是如何保证先运行类中的__new__方法再运行类中的__init__方法的?上述问题的答案就是类中定义的__call__方法,如果想让一个对象变成一个可调用对象(加括号可以调用),需要在该对象的类中定义__call__方法,调用可调用对象的返回值就是__call__方法的返回值。

class Test():
    
    def __init__(self):
        self.name = 'python'
    
    def __call__(self, *args, **kwargs):  # self是Test类的对象
        print(self)  # <__main__.Test object at 0x000001C78CE78FD0>
        print(self.name)
        
t = Test()
t()  # python

因此我们可以得到以下结论:

对象加括号调用会调用该对象的类中定义的__call__方法;

类加括号调用会调用内置元类或自定义元类中的__call__方法,取决于类的元类是什么;

自定义元类加括号调用会内置元类中的__call__方法。

 

我们可以简单验证一下上述结论:

class MyMeta(type):
 
    def __call__(self, *args, **kwargs): 
        print(self)
        print(args)
        print(kwargs)
        
        return 'test'

class Test(metaclass=MyMeta):
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 调用Test就调用了
t = Test()
print(t)

'''
<class '__main__.Test'>
('haha', '123')
{}
test
'''

通过上述代码我们可以推断出调用Test时,会调用自定义元类中的__call__方法,并将返回值赋值给调用类产生的对象。

我们也可以通过__call__方法自定义元类来控制类的调用,也就是产生对象。

class Mymeta(type):

    def __init__(self, class_name, class_bases, class_dict):
        
        # 实现类名首字母必须大写,否则抛出异常
        if not class_name.istitle():
            raise NameError('类名的首字母必须大写')
        # 实现创建的类必须有文档注释,否则抛出异常
        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip())==0:
            raise TypeError('必须有文档注释')

    def __new__(cls, *args, **kwargs):

        return type.__new__(cls, *args, **kwargs)

    def __call__(self, *args, **kwargs):
        # self指的就是当前类产生的对象
        people_obj = self.__new__(self)
        self.__init__(people_obj,*args, **kwargs)
        return people_obj
    
Test = MyMeta(class_name, class_bases, class_dic)  # 调用的是内置元类type的__call__方法

class Test(metaclass=Mymeta):
    """
    我是文档注释
    """
    def __new__(cls, *args, **kwargs):
        # 产生空对象--真正的对象,真正造对象的是object
        return object.__new__(cls)  # 这里使用type也没有问题
    
    def __init__(self,name):
        self.name = name
   
    
t = Test()  # 调用的是自定义元类中的__call__方法

通过自定义元类来创建类的时候,会调用type的__call__方法,该方法内部会做三件事情:

type.__call__做的事情:

1、先调用自定义元类中的__new__方法,产生一个空对象

2、在调用自定义元类中的__init__方法,为空对象添加独有属性

3、返回一个初始化好的自定义元类的对象,就是上述的Test类

 

调用Test类时则会调用自定义元类MyMeta.__call__方法,同样也会做三件事:

MyMeta.__call__做的事情:

1、先调用 Test类中的__new__方法产生一个空对象

2、再调用Test 类中的__init__方法为空对象添加独有属性

3、返回一个初始化好的Test类的对象赋值给t

转载请注明:XAMPP中文组官网 » python一切皆对象- 元类详解

您必须 登录 才能发表评论!