23. 元类
1. 元类的概念
python中一切皆对象,八大基本数据类型是对象,类实例化得到的对象也是对象;类本身也是一种对象
type(python自带的元类)---元类metaclass(自定义的元类)---类(class)---对象(obj)
元类,即高于用class
定义的类的类,被称为metaclass
(元类),其中type
就是Python自带的元类。这意味着我们可以手动创建type
的实例作为类的定义,或者通过定义派生自type
的元类,对其进行修改,以实现更高级的功能和灵活性。
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())
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,...),{数据属性名:数据属性值,函数属性名:函数属性值,...})
示例
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):
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):
print('自定义元类中的__new__被调用')
print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}')
obj2 = type.__new__(cls, *args, **kwargs) # 产生的是一个空的类
print(obj2)
return obj2
class Student(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 __new__(cls, *args, **kwargs): print('自定义元类中的__new__被调用') print(f'可变长位置参数:{args}, 可变长关键字参数:{kwargs}') obj2 = type.__new__(cls, *args, **kwargs) # 产生的是一个空的类,是外界继承该元类时的类 print(obj2) return obj2 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='avril') # 自定义元类中的__new__被调用 # 可变长位置参数:('Student', (), {'__module__': '__main__', '__qualname__': 'Student', '__init__': <function Student.__init__ at 0x00000249538117E0>, '__call__': <function Student.__call__ at 0x0000024953811870>}), 可变长关键字参数:{} # <class '__main__.Student'> # 自定义元类中的__init__被调用 # ------------------------------------------------------------------------- # 自定义元类中的__call__被调用 # 可变长位置参数:(), 可变长关键字参数:{'name': 'avril'} # 产生对象的类中的__init__方法被调用 # <__main__.Student object at 0x0000024953803D00>