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

元类(metaclass)

程序员文章站 2022-06-28 23:50:25
[TOC] 引言 元类属于python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也只是自圆其说、点到为止,从对元类的控制上来看就破绽百出、逻辑混乱,今天我就来带大家来深度了解python元类的来龙去脉。 笔者深入浅出的背后是对技术一日复一日的执念,希望可以大家可以尊重 ......

目录

元类(metaclass)

引言

  • 元类属于python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也只是自圆其说、点到为止,从对元类的控制上来看就破绽百出、逻辑混乱,今天我就来带大家来深度了解python元类的来龙去脉。

  • 笔者深入浅出的背后是对技术一日复一日的执念,希望可以大家可以尊重原创,为大家能因此文而解开对元类所有的疑惑而感到开心!!!

什么是元类

  • 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类
class foo:  # foo=元类()
    pass

元类(metaclass)

为什么用元类

  • 元类是负责产生类的,所以我们学习元类或者自定义元类的目的:是为了控制类的产生过程,还可以控制对象的产生过程

内置函数exec(储备)

cmd = """
x=1
print('exec函数运行了')
def func(self):
    pass
"""
class_dic = {}
# 执行cmd中的代码,然后把产生的名字丢入class_dic字典中
exec(cmd, {}, class_dic)
exec函数运行了
print(class_dic)
{'x': 1, 'func': <function func at 0x10a0bc048>}

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'>

元类(metaclass)

type实现

  • 创建类的3个要素:类名,基类,类的名称空间

  • people = type(类名,基类,类的名称空间)

class_name = 'people'  # 类名

class_bases = (object, )  # 基类

# 类的名称空间
class_dic = {}
class_body = """
country='china'
def __init__(self,name,age):
    self.name=name
    self.age=age
def eat(self):
    print('%s is eating' %self.name)
"""

exec(
    class_body,
    {},
    class_dic,
)
print(class_name)
people
print(class_bases)
(<class 'object'>,)
print(class_dic)  # 类的名称空间
{'country': 'china', '__init__': <function __init__ at 0x10a0bc048>, 'eat': <function eat at 0x10a0bcd08>}
  • people = type(类名,基类,类的名称空间)
people1 = type(class_name, class_bases, class_dic)
print(people1)
<class '__main__.people'>
obj1 = people1(1, 2)
obj1.eat()
1 is eating
  • class创建的类的调用
print(people)
<class '__main__.people'>
obj = people1(1, 2)
obj.eat()
1 is eating

元类(metaclass)

自定义元类控制类的创建

  • 使用自定义的元类
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__实现了,实例化对象可以加括号调用了

自定义元类控制类的实例化

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的空对象,申请内存空间
        obj = self.__new__(self)
        # 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)
#         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}

元类(metaclass)

自定制元类后类的继承顺序

class mymeta(type):
    n = 444


#     def __call__(self, *args, **kwargs):
#         obj = self.__new__(self)  # self = foo
#         # obj = object.__new__(self)  # self = foo
#         self.__init__(obj, *args, **kwargs)
#         return obj


class a(object):
    #     n = 333
    pass


class b(a):
    #     n = 222
    pass


class foo(b, metaclass=mymeta):  # foo = mymeta(...)
    #     n = 111

    def __init__(self, x, y):
        self.x = x
        self.y = y


for i in foo.mro():
    print(i)
<class '__main__.foo'>
<class '__main__.b'>
<class '__main__.a'>
<class 'object'>
print(foo.n)
444
  • 查找顺序:

    1. 先对象层:foo->b->a->object

    2. 然后元类层:mymeta->type

print(type(object))
<class 'type'>
obj = foo(1, 2)
print(obj.__dict__)
{'x': 1, 'y': 2}

元类(metaclass)

使用元类修改属性为隐藏属性

class mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        # 加上逻辑,控制类foo的创建
        super(mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):
        # 加上逻辑,控制foo的调用过程,即foo对象的产生过程
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        # 修改属性为隐藏属性
        obj.__dict__ = {
            '_%s__%s' % (self.__name__, k): v
            for k, v in obj.__dict__.items()
        }

        return obj
class foo(object, metaclass=mymeta):  # foo = mymeta(...)
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex


obj = foo('egon', 18, 'male')
print(obj.__dict__)
{'_foo__name': 'egon', '_foo__age': 18, '_foo__sex': 'male'}