23. 元类

1. 元类的概念

python中一切皆对象,八大基本数据类型是对象,类实例化得到的对象也是对象;类本身也是一种对象

type(python自带的元类)---元类metaclass(自定义的元类)---类(class)---对象(obj)

元类,即高于用class定义的类的类,被称为metaclass(元类),其中type就是Python自带的元类。这意味着可以手动创建type的实例作为类的定义,或者通过定义派生自type的元类,对其进行修改,以实现更高级的功能和灵活性。

type查看的其实是当前对象所属的类的名称

print(type(1))
print(type('a'))
print(type([3, 6, 9]))
class Student():
    def __init__(self, name):
        self.name = name


# 1.实例化类得到对象
stu1 = Student(name='lavigne')
# 2.查看对象的数据类型
print(type(stu1))  # <class '__main__.Student'>
# 3.查看这个类的数据类型
print(type(Student))  # <class 'type'>
# 4.查看产生字典、列表、字符串的方法的数据类型
print(type(dict))  # <class 'type'>
print(type(list))  # <class 'type'>
print(type(str))  # <class 'type'>

通过查看每一种数据的数据类型,会发现都共同拥有一个类,这个类就是type称之为元类

2. 产生类的两种方式

2.1 关键字创建

语法

class 类名(基类):  # 基类默认object 
    # 类体代码

 

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

# 查看类的元类
print(type(Student))  # <class 'type'>

2.2 通过type创建

语法

# 查看type的__init__方法
# print(type())

image

image

def __init__(cls, what, bases=None, dict=None):  # known special case of type.__init__
    """
    type(object) -> the object's type  # 返回对象的类型
    type(name, bases, dict, **kwds) -> a new type  # 产生一个新的类
    # (copied from class doc)
    """
    pass
# 类名 = type('类名',(父类1,父类2,...),{数据属性名:数据属性值,函数属性名:函数属性值,...})

示例:定义一个类,类名是Student,基类是object,名称空间{name,read}

def read():
    ...

Student = type('Student', (object,), {'name': 'lavigne', 'read': read})  # 自动按位置传入
print(type(Student))  # <class 'type'>
print(Student.__dict__)

# {'name': 'lavigne', 'age': 20, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>,
'__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
class Student: name = 'lavigne' read = read print(type(Student)) # <class 'type'> print(Student.__dict__) # {'__module__': '__main__', 'name': 'lavigne', 'age': 20, '__dict__': <attribute '__dict__' of 'Student' objects>,
'__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}

使用关键字class定义的类和使用打印类型的type定义的类属性字典一致

3. 为什么要使用元类

类用来控制对象的产生

元类用来控制类的产生,可以定制类的具体数据

4. 元类的使用

4.1 问题引出

要求所有产生的类的名字都是大写的

4.2 推导

对象的产生过程,是通过类里面的_ _init_ _方法实现的

猜测:在元类里面也有一个_ _init_ _方法

4.3 基本使用

# def __init__(cls, what, bases=None, dict=None):  # known special case of type.__init__
#     """
#     type(object) -> the object's type
#     type(name, bases, dict, **kwds) -> a new type
#     # (copied from class doc)
#     """
#     pass

# 1.重写元类中的__init__方法,产生一个自定义元类
class NewType(type):
    def __init__(cls, name, bases, dct):
      """
      :param name:类名
      :param bases:类的基类
      :param dct:类的名称空间
      """
     print(f'自定义的元类__init__被调用,新类的名称是:{name}')
        print(f'自定义的元类__init__被调用,新类的基类是:{bases}')
        print(f'自定义的元类__init__被调用,新类的属性字典是:{dct}')
        super().__init__(name, bases, dct)  # 三个参数只是产生新类时实参与系统元类type的中间值,可以与type中的名字不一样

# 2.元类的使用采用metaclass关键字声明
class Student(metaclass=NewType):  # 3.创建一个新的类
    name = 'lavigne'

# 4.实例化类、产生一个对象
stu1 = Student()
# 自定义的元类__init__被调用,新类的名称是:Student
# 自定义的元类__init__被调用,新类的基类是:()
# 自定义的元类__init__被调用,新类的属性字典是:{'__module__': '__main__', '__qualname__': 'Student', 'name': 'lavigne'}
print(Student.__bases__)  # (<class 'object'>,)


# 5.在创建新的类时稍作修改,给类的参数里面传入object,观察自定义的元类中bases参数的和类的bases参数变化 class Teacher(object, metaclass=NewType): name = 'avril' Teacher() # 自定义的元类__init__被调用,新类的名称是:Teacher # 自定义的元类__init__被调用,新类的基类是:(<class 'object'>,) # 相比于方法一多了传入的实参 # 自定义的元类__init__被调用,新类的属性字典是:{'__module__': '__main__', '__qualname__': 'Teacher', 'name': 'avril'} print(Teacher.__bases__) # (<class 'object'>,) # 相比于方法一无变化

4.4 进阶使用---由元类产生类名时加限制条件

要求:产生的类的名称字母必须大写

class NewType(type):
    def __init__(cls, name, bases, dct):
        print(f'自定义的元类__init__被调用,新类的名称是:{name}')
        print(f'自定义的元类__init__被调用,新类的基类是:{bases}')
        print(f'自定义的元类__init__被调用,新类的属性字典是:{dct}')
        if not name.isupper():
            raise TypeError(f'类名{name}字母必须大写')
        super().__init__(name, bases, dct)


# 1.创建一个类
class student(metaclass=NewType):  # TypeError: 类名student字母必须大写
    ...


class Teacher(metaclass=NewType):  # TypeError: 类名Teacher字母必须大写
    ...


class STUDENT(metaclass=NewType):  # 符合规范的会被正常调用
    ...
# 自定义的元类__init__被调用,新类的名称是:STUDENT
# 自定义的元类__init__被调用,新类的基类是:()
# 自定义的元类__init__被调用,新类的属性字典是:{'__module__': '__main__', '__qualname__': 'STUDENT'}

5. 元类的进阶使用

5.1 引入

之前学过的_ _call_ _方法,在类内部定义一个_ _call_ _方法,对象加括号会自动执行产生该对象类里面的_ _call_ _方法,并得到对应的返回值

 

类加括号调用:同理也会触发元类中的_ _call_ _方法,从而获得返回值,这个返回值正是实例化得到的对象

class NewType(type):
    def __init__(cls, name, bases, dct):
        print('自定义元类中的__init__被调用')
        super().__init__(name, bases, dct)

    def __call__(cls, *args, **kwargs):
        print('自定义元类中的__call__被调用')
        print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}')
        obj = super().__call__(*args, **kwargs)
        print(obj)
        return obj  # 将产生的对象返回出去


class Student(metaclass=NewType):
    def __init__(self, name):
        print('产生对象的类中的__init__方法被调用')
        self.name = name

    def __call__(self, *args, **kwargs):
        print('产生对象的类中的__call__方法被调用')
        return '类中call方法的返回值'


stu1 = Student(name='lavigne')
# 自定义元类中的__init__被调用
# 自定义元类中的__call__被调用
# 可变长位置参数:(), 可变长关键字参数:{'name': 'lavigne'}
# 产生对象的类中的__init__方法被调用
# <__main__.Student object at 0x0000020A3D043D60>
print(stu1)
# <__main__.Student object at 0x0000020A3D043D60>
# 可以发现自定义元类的__call__的返回值obj和对象stu1一致

print(stu1())
# 产生对象的类中的__call__方法被调用
# 类中call方法的返回值

5.2 定制对象的产生过程

只能通过关键字不能通过位置传参

class NewType(type):
    def __init__(cls, name, bases, dct):
        print('自定义元类中的__init__被调用')
        super().__init__(name, bases, dct)

    def __call__(cls, *args, **kwargs):
        print('自定义元类中的__call__被调用')
        print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}')
        if args:
            raise TypeError(f'产生对象只能通过关键字传参,不能通过位置传参')
        obj = super().__call__(*args, **kwargs)
        print(obj)
        return obj  # 将产生的对象返回出去


class Student(metaclass=NewType):
    def __init__(self, name):
        print('产生对象的类中的__init__方法被调用')
        self.name = name

    def __call__(self, *args, **kwargs):
        print('产生对象的类中的__call__方法被调用')
        return '类中call方法的返回值'


# # 正确的方法:
stu1 = Student(name='lavigne')
# 自定义元类中的__init__被调用
# 自定义元类中的__call__被调用
# 可变长位置参数:(), 可变长关键字参数:{'name': 'lavigne'}
# 产生对象的类中的__init__方法被调用
# <__main__.Student object at 0x000002127EBA3D60>
print(stu1)
# <__main__.Student object at 0x0000026D97243D60>
print(stu1())
# 产生对象的类中的__call__方法被调用
# 类中call方法的返回值


# 错误的方法:
stu2 = Student('avril')
# 自定义元类中的__init__被调用
# 自定义元类中的__call__被调用
# 可变长位置参数:('avril',), 可变长关键字参数:{}
# Traceback (most recent call last):
#   File "D:\project\pythonProject\4.py", line 41, in <module>
#     stu2 = Student('avril')
#   File "D:\project\pythonProject\4.py", line 10, in __call__
#     raise TypeError(f'产生对象只能通过关键字传参,不能通过位置传参')
# TypeError: 产生对象只能通过关键字传参,不能通过位置传参

6. 总结

如果要定制类的产生过程,那么编写自定义元类中的_ _init_ _方法

如果要定制对象的产生过程,那么编写自定义元类中的_ _call_ _方法

7. new方法与call方法

7.1 概念

_ _new_ _ 方法用于在自定义元类中产生空类,相当于框架

_ _ init_ _ 方法在自定义元类中用于产生类,在类中用于产生对象,相当于外观和各种属性

7.2 自定义元类中的_ _new_ _直接调用

并不是所有的地方都可以直接调用_ _new_ _,该方法过于底层

如果是在元类的_ _new_ _里面,可以直接调用

class NewType(type):
    def __init__(cls, name, bases, dct):
        print('自定义元类中的__init__被调用')
        super().__init__(name, bases, dct)

    def __call__(cls, *args, **kwargs):
        print('自定义元类中的__call__被调用')
        print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}')
        obj = super().__call__(*args, **kwargs)
        print(obj)
        return obj  # 将产生的对象返回出去

    # 初始化时处罚,初始化框架
    def __new__(cls, *args, **kwargs):
        """
        :param args:类名、类的基类、类的名称空间,与type()调用方式一样
        :param kwargs:
        """
        print('自定义元类中的__new__被调用')
        print(f'元类new方法的cls参数:{cls}')
        print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}')
        # 调用type的__new__方法将参数和类传给type进行处理,创建出一个类的框架
        obj2 = type.__new__(cls, *args, **kwargs)  # 产生的是一个空的类
        print(obj2)
        return obj2


class Student(object, metaclass=NewType):
    def __init__(self, name):
        print('产生对象的类中的__init__方法被调用')
        self.name = name

    def __call__(self, *args, **kwargs):
        print('产生对象的类中的__call__方法被调用')
        return '类中call方法的返回值'
只要定义了类,即使没向该类传参产生对象,执行以上代码也会调用__new__方法:
# 自定义元类中的__new__被调用
# 可变长位置参数:('Student', (), {'__module__': '__main__', '__qualname__': 'Student', '__init__': <function Student.__init__ at 0x0000021A3BFE17E0>,
#               '__call__': <function Student.__call__ at 0x0000021A3BFE1870>}), 可变长关键字参数:{}
# <class '__main__.Student'>
# 自定义元类中的__init__被调用

stu1 = Student(name='lavigne')
# 自定义元类中的__new__被调用
# 可变长位置参数:('Student', (), {'__module__': '__main__', '__qualname__': 'Student', '__init__': <function Student.__init__ at 0x000001BB2D6417E0>, 
#              '__call__': <function Student.__call__ at 0x000001BB2D641870>}), 可变长关键字参数:{}
# <class '__main__.Student'>
# 自定义元类中的__init__被调用
----------------------------------------------------------------------
# 自定义元类中的__call__被调用
# 可变长位置参数:(), 可变长关键字参数:{'name': 'lavigne'}
# 产生对象的类中的__init__方法被调用
# <__main__.Student object at 0x000001BB2D633D00>

7.3 自定义元类中_ _call_ _间接调用

class NewType(type):
    def __init__(cls, name, bases, dct):
        print('自定义元类中的__init__被调用')
        super().__init__(name, bases, dct)

    def __call__(cls, *args, **kwargs):
        print('自定义元类中的__call__被调用')
        print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}')
        obj = super().__call__(*args, **kwargs)
        print(obj)
        return obj  # 将产生的对象返回出去

    # 初始化时处罚,初始化框架
    def __new__(cls, *args, **kwargs):
        """
        :param args:类名、类的基类、类的名称空间,与type()调用方式一样
        :param kwargs:
        """
        print('自定义元类中的__new__被调用')
        print(f'元类new方法的cls参数:{cls}')
        print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}')
        # 调用type的__new__方法将参数和类传给type进行处理,创建出一个类的框架
        obj2 = type.__new__(cls, *args, **kwargs)  # 产生的是一个空的类
        print(obj2)
        return obj2


class Student(object, metaclass=NewType):
    def __init__(self, name):
        print('产生对象的类中的__init__方法被调用')
        self.name = name

    def __call__(self, *args, **kwargs):
        print('产生对象的类中的__call__方法被调用')
        return '类中call方法的返回值'

    def __new__(cls, *args, **kwargs):
        print('产生对象的类中的__new__方法被调用')
        print(f'产生对象的类的__new__函数的cls{cls}')
        print(f'位置参数{args},关键字参数{kwargs}')
        obj = object.__new__(cls)
        obj.__init__(*args, **kwargs)  # 显式调用了init
        return obj

stu1 = Student(name='lavigne')
自定义元类中的__new__被调用
元类new方法的cls参数:<class '__main__.NewType'>
可变长位置参数:('Student', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'Student', '__init__': <function Student.__init__ at 0x0000022E0B461750>, '__call__': <function Student.__call__ at 0x0000022E0B461870>, '__new__': <function Student.__new__ at 0x0000022E0B461900>}), 可变长关键字参数:{}
<class '__main__.Student'>
自定义元类中的__init__被调用
------------------------------------------------------------------- 自定义元类中的__call__被调用 可变长位置参数:(), 可变长关键字参数:{
'name': 'lavigne'} 产生对象的类中的__new__方法被调用 产生对象的类的__new__函数的cls<class '__main__.Student'> 位置参数(),关键字参数{'name': 'lavigne'} 产生对象的类中的__init__方法被调用 产生对象的类中的__init__方法被调用 <__main__.Student object at 0x0000022E0B467C70>

重复打印原因:

在Student.__new__中,显式调用了obj.__init__(*args, **kwargs),而实际上,在Python中,当__new__创建了实例后,Python的__call__方法会自动调用__init__方法。

所以,整个调用流程是这样的:

调用NewType.__call__(因为Student是NewType的实例)
NewType.__call__调用super().__call__(*args, **kwargs),即type.__call__(*args, **kwargs)
type.__call__调用Student.__new__来创建实例
Student.__new__创建了实例并显式调用了obj.__init__(*args, **kwargs)(即Student.__init__)
type.__call__再次调用Student.__init__(因为type.__call__在创建实例后会自动调用__init__)
因此,Student.__init__被调用了两次,导致打印了两遍。

正确的做法应该是:

def __new__(cls, *args, **kwargs):
    print('产生对象的类中的__new__方法被调用')
    print(f'产生对象的类的__new__函数的cls{cls}')
    print(f'位置参数{args},关键字参数{kwargs}')
    return object.__new__(cls)  # 不要在这里调用__init__

这样,__init__只会被type.__call__自动调用一次。

总结一下Python中对象创建的流程:
调用元类的__call__方法
元类的__call__调用type.__call__
type.__call__调用类的__new__方法
type.__call__在__new__返回后调用类的__init__方法
所以,类的__new__中不应该再显式调用__init__,否则会导致__init__被调用两次。

 

定义类的调用流程:

1.元类的_ _new_ _方法初始化类的框架,返回一个生成的类对象

2.元类的_ _init_ _方法,传入参数(类名、类的基类、类的名称空间)进行类的属性初始化,无需返回

类初始化生成对象的调用流程:

1.调用元类的_ _call_ _方法(开始)

2.当前类的_ _new_ _方法,创建对象的框架

3.当前类的_ _init_ _方法,初始化对象的属性

4.回到元类的_ _call_ _方法(结束),返回初始化生成的对象

总结:

1.如果控制类的产生,重写元类的_ _new_ _和_ _init_ _方法

2.如果控制对象的产生,重写元类的_ _call_ _方法

posted @ 2024-08-20 23:57  jerrry丶deng  阅读(69)  评论(0)    收藏  举报