欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python-day29(正式学习)

程序员文章站 2022-05-30 18:37:41
[TOC] 元类 警告!警告!前方核能来袭!!!想要上车的小伙伴赶紧上车了,时间不多了。。。列车长注意把各车门给我焊死了!请各位小伙伴在路途中勿随意跳车,跳车不规范,亲人两行泪。。。各位乘客我们开始出发了,路途中请注意生命安全,如未及时写下遗书,本列车概不负责! 什么是元类 二、什么是元类 在pyt ......

目录

元类

警告!警告!前方核能来袭!!!想要上车的小伙伴赶紧上车了,时间不多了。。。列车长注意把各车门给我焊死了!请各位小伙伴在路途中勿随意跳车,跳车不规范,亲人两行泪。。。各位乘客我们开始出发了,路途中请注意生命安全,如未及时写下遗书,本列车概不负责!

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

python-day29(正式学习)