zoukankan      html  css  js  c++  java
  • day29

    什么是元类
    二、什么是元类

    在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自定义类的运行原理(而非元类的的运行原理):
    拿到一个字符串格式的类名class_name='People'
    拿到一个类的基类们class_bases=(obejct,)
    执行类体代码,拿到一个类的名称空间class_dic={...}
    调用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的目的
    先造出一个People的空对象
    为该对空对象初始化独有的属性
    返回一个初始化好的对象
    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
    查找顺序:
    先对象层:OldoyTeacher->Foo->Bar->object
    然后元类层: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__

  • 相关阅读:
    jmeter脚本开发:插件安装和设计场景(五)
    jmeter脚本开发:SOAP接口和JDBC(四)
    jmeter脚本开发:控制器和参数化(三)
    gauge自动化框架踩坑(六):关于csv
    gauge自动化框架踩坑(五):关于表格
    gauge自动化框架踩坑(四):在测试报告中自定义messages
    MediaPlayer播放音频,也可以播放视频
    soundpool播放声音
    ContentProvider
    了解 IMyInterface.Stub
  • 原文地址:https://www.cnblogs.com/bjlxxbj/p/11508213.html
Copyright © 2011-2022 走看看