元类

元类

一、什么是元类

  • 元类属于python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也只是自圆其说、点到为止,从对元类的控制上来看就破绽百出、逻辑混乱,今天我就来带大家来深度了解python元类的来龙去脉。
  • 笔者深入浅出的背后是对技术一日复一日的执念,希望可以大家可以尊重原创,为大家能因此文而解开对元类所有的疑惑而感到开心!!!
  • 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类

简单说只要继承了type他就是元类

class Person:  # Foo=元类()
    pass

元类 type—实例化--->类Foo ---实例化类--> 对象

114-元类metaclass-类的创建.png?x-oss-process=style/watermark

Person类也是个对象,那他一定是由一个类实例化得到的,这个类就叫元类

1.1 如何找元类

print(type(p1))

# 同理:type类是产生所有类的元类
print(type(Person))
print(type(list))
print(type(dict))
print(type(object))


# 内置函数
print(type(print))
<class '__main__.Person'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'builtin_function_or_method'>

二、为什么用元类

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

三、 内置函数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 加类名,就会把类构造出来
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元类

Person 类是有type实例化产生的,向type传了一堆参数,然后type() 调用类的__init__方法,就会创建一个类,

type()创建类格式:

type(object, bases(继承的基类), dict)

object: object (所有的基类) or name (类名:你所要创建的名字),类的名字, 是个字符串;

base: 是他的所有的父类(元组的形式)、基类;
dict: 名称空间,是一个字典;

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

通过type直接产生的类,不用class关键字;

l = {}
g = {}
exec("""
school='hnnu'
def __init__(self,name):
    self.name=name
def score(self):
    print('分数是100')
""", g, l)

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


# 创建类 People = type(类名,基类,类的名称空间)
Person = type('Person', (object, ), {'school': 'hnnu', '__init__': __init__})

# 创建对象
p = Person('randy')
print(p.name)
# 创建类
Person = type('Person', (object,), l)
print("*"*9)
print(Person)
print("*"*9)

print()

type('Person', (object,), l)
# # 获取属性值
print(Person.school)
#
print(Person.__dict__)
#
print(Person.__bases__)
print(Person.mro())

七、通过元类来控制类的产生

自定义元类:来控制类的产生, 可以控制类名, 可以控制类的继承父类, 控制类的名称空间
自定制的元类必须继承type,写一个类继承type,这种类就叫元类

class Mymeta(type):

    # def __init__(self, *args, **kwargs):

    def __init__(self, name, base, dic):
        print(name) # 类名
        print(base) # 基类
        print(dic) # 名称空间

"""
通过 class创建的类继承,自定义的元类, 在Person中定义第一个参数表示类的名称,第二个参数表示继承的基类
,而类中的代码则是,产生的名称空间,
"""
class Person(object, metaclass=Mymeta):
    school = 'hnnu'

    def __init(self, name):
        self.name = name

    def score(self):
        print("分数是: 100")


p = Person()

7.1 控制类的产生

# 练习一类名以sb开头
class Mymeta(type):

    def __init__(self, name, base, dic):
        # 练习一 加限制, 控制类名必须以sb开头
        print(name)
        if not name.startswith('sb'):
            raise Exception("类名没有以sb开头")


class sb_Person(object, metaclass=Mymeta):
    school = 'hnnu'

    def __init__(self, name):
        self.name = name
        print(2)

    def score(self):
        print("分数是: 100")


p1 = sb_Person('ran')


# 练习二, 类必须有注释
class Mymeta(type):

    def __init__(self, name, base, dic):
        print(self.__dict__)  # 产生的名称空间是类
        print(dic)
        doc = self.__dict__['__doc__']
        print("元类")
        if not doc:
            raise Exception("必须有注释")

        print(doc)


class Person(object, metaclass=Mymeta):
    """
    我已经注释了
    """
    school = 'hnnu'

    def __init__(self, name):
        print("自己")
        self.name = name

    def score(self):
        print("分数是: 100")


# 继承元类,首先会先进入元类中的__init__在返回来执行自己的
p1 = Person('ran')

继承元类,首先会先进入元类中的__init__在返回来执行自己的

7.2 控制类产生的模板

#  控制类产生模板
class Mymeta(type):
    def __init__(self, name, base, dic):
        if self.name == Person:
            raise Exception("名称错误")


class Person(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age


p = Person('laowang', 19)

八、通过元类控制类的调用过程(控制创建类对象的过程)

要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法、__call__方法,该方法会在调用对象时自动触发

我们之前说类实例化第一个调用的是__init__,但__init__其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 __new__ 方法。

__new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

8.1 __call__控制类的调用过程

# 创建元类
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(self)
        print(self.__dict__)
        print(args)
        print(kwargs)
        print('进入__call__')
        return 123

# 创建类
class Person(object, metaclass=Mymeta):
    school = 'hnnu'

    def __init__(self, name):
        print('person__init__')
        self.name = name

    def score(self):
        print("分数100")

    def __call__(self, *args, **kwargs):
        print('Person 进入__call__')
        return 1
<class '__main__.Person'>
('randy',)
{}
进入元类__call__
123

8.2 __call__控制类的调用过程实现

class Mymeta(type):
   
    def __call__(self, *args, **kwargs):

        """
        因为__call__返回的空对象,所以要自己生成对象,通过__new方式)
        :param self: self ==> Person
        :param args: Person(randy)
        :param kwargs: Person(age=18)
        :return:
        """
        print('进入元类__call__')

        # self ==> Person

        # 出现递归的原因 self()加括号运行相当于Person()所以还是调用元类的__call__方法
        # return self(*args) # 出现递归

        # 实例化产生一个Person类的对象,借助__new__来产生,需要把类传过去,才能产生对象
        # 方式一(调用父类object中的__new__)
        self.__new__(self)
        # obj 是Person类的对象,只不过是空的,
        # 方式二
        obj = object.__new__(self)  # (因为__call返回的空对象,所以要自己生成对象,通过__new方式)

        # obj = self.__new__(self)

        # 调用__init__方法完成初始化
        # 类来调用__init__方法, 就是个普通函数,有几个参数就要传几个参数
        # self.__init__(obj, *args, **kwargs)

        # 对象来调用__init__方法,对象的绑定方法,会把自身传过去
		
        kwargs['age'] = 18
        
        obj.__init__(*args, **kwargs)

        print(obj)
        return obj


class Person(object, metaclass=Mymeta):
    school = 'hnnu'

    def __init__(self, name, *args,**kwargs):
        print('person__init__')
        self.name = name

    def score(self):
        print("分数100")

    def __call__(self, *args, **kwargs):
        print('Person 进入__call__')
        return 1


p = Person('randy')
print(p.__dict__)
print(Person.__dict__)
p.name
进入元类__call__
person__init__ 2 
{'age': 18}
<__main__.Person object at 0x038D4D70>
{'name': 'randy'}
{'__module__': '__main__', 'school': 'hnnu', '__init__': <function Person.__init__ at 0x0C68FF18>, 'score': <function Person.score at 0x0C68FED0>, '__call__': <function Person.__call__ at 0x0C68FE88>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        # self 就是Person类
        print(2)
        print(self.mro())  # 类对象的属性查找顺序
        # obj=self.__new__(self)  # 方法1,通过self的new方法创建对象
        obj = object.__new__(self)  # 方法2,通过object的new方法创建对象
        obj.__init__(*args, **kwargs)  # 调用对象的init方法
        return obj


    def __init__(self, *args):
        print(3)
        print(args)


class Person(metaclass=Mymeta):  # 实际上是先调用元类的call方法,再回调自定义元类的init方法
    age = 33  # 39;18&#39;
    def __init__(self, name):  # 当类实例化生成对象后,会通过call调用init方法
        print(1)
        self.name = name


p = Person(name= 'randy') # 先调用自定义元类的call方法
print(p.__dict__)
print(p.name)
print(p)
3
('Person', (), {'__module__': '__main__', '__qualname__': 'Person', 'age': 33, '__init__': <function Person.__init__ at 0x0C017198>})
2
[<class '__main__.Person'>, <class 'object'>]
1
{'name': 'randy'}
randy
<__main__.Person object at 0x03263730>

总结:

  1. __call__的第一个参数self是创建类的类名,第二、三参数为创建对象属性的值;
  2. 直接返回return self(*args) 会出现递归原因在于,self(*args)相当于Person(*args)最终还是调用元类的 __call__,所以会出现死循环;解决办法,创建类对象调用自己的__init__方法
# 方式一
obj = object.__new__(self)  # (因为__call返回的空对象,所以要自己生成对象,通过__new方式)
# obj = object.__new__(Person)

obj.__init__(*args, **kwargs) # 调用Person类中自己的__init__方法
return obj
# 方式三
obj =  self.__new__(self) # 调用还是object中的__new__方法
obj.__init__(*args, **kwargs) #等同与 self.__init__(obj, *args, **kwargs) 调用Person类中自己的__init__方法
return obj


print(self.__new__ is object.__new__) #True

8.3 当前对象的__call__

class Person1():
    school = 'oldboy'

    def __init__(self, name):
        print(1)
        self.name = name

    def score(self):
        print('分数是100')

    def __call__(self, *args, **kwargs):
        print('xxxx')

# 自动调用触发init的执行
p = Person1('randu')

# 触发自己的__call__
p()

print('xxxx')

8.4 __call__ 最终版

注意:__new()__ 函数只能用于从object继承的新式类。

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('randy', age=18)
    

    <class '__main__.People'>
    ('randy',)
    {'age': 18}

    print(obj.__dict__)
    

    {'name': 'randy', 'age': 18}

九、把对象属性都变成私有

class Mymeta(type):
    def __call__(self, *args, **kwargs):

        print("__call__ 第一")
        # 产生空对象
        obj = object.__new__(self)
        # 调用子类__init__
        obj.__init__(*args, **kwargs)
		
        # 名称空间创建完成之后进行处理
        # 会进入self==> person类中__init__然后在回来
        obj.__dict__ = {f"_{self.__name__}__{k}": v for k, v in obj.__dict__.items()}

        print("123456", obj.__dict__)
        print("__call__ ", obj.__dict__)

        return obj


class Person(object, metaclass=Mymeta):
    def __init__(self, name):
        print("___init__ 第二")
        self.name = name


    def score(self):
        print('分数是100')


p = Person(name='randy')
print(p.__dict__)
print(p._Person__name)

p = Person(name='randy')
print(p.__dict__)
print(p._Person__name)
__call__ 第一
___init__ 第二
123456 {'_Person__name': 'randy'}
__call__  {'_Person__name': 'randy'}
{'_Person__name': 'randy'}
randy

十、自定义元类后继承顺序

结合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__的查找

总结:

  1. 类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错
  2. 对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错

10.1 练习

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('randy', 18, 'male')

print(obj.__dict__)

十一、总结

继承元类,首先会先进入元类中的__init__在返回来执行自己的

控制类产生模板

#  控制类产生模板
class Mymeta(type):
    def __init__(self, name, base, dic):
        if self.name == Person:
            raise Exception("名称错误")


class Person(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age


p = Person('laowang', 19)

控制对象的产生

class Mymeta(type):
    def __call__(self, *args, **kwargs):

        # 第一步产生空对象

        obj=object.__new__(self)
        # 第二部初始化空对象,把初始值放到对象中
        obj.__init__(*args, **kwargs)
        # 第三步返回对象
        return obj

class Person(metaclass=Mymeta):
    def __init__(self,name):
        self.name=name
    def __call__(self, *args, **kwargs):
        print('xxx')

p=Person('randy')

对象查找顺序

类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错

对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错

在当下的阶段,必将由程序员来主导,甚至比以往更甚。
原文地址:https://www.cnblogs.com/randysun/p/11455526.html