Python 面向对象之元类
【一】一切皆对象
【1】元类
- 元类(metaclass)是Python中用于创建类的类。
- 在Python中,类是对象,而元类就是类的类
- 它们控制类的创建过程,允许你定制类的行为
- Python中内置的默认元类是type
- 我们用class关键字定义的所有类以及内置的类都是有元类type实例化产生
【2】class机制
- class是Python的关键字,目的用来创建类
- 它在底层实现类的定义本质上有四个步骤 
  - 获取类名
- 获取基类
- 获取类的名称空间
- 调用元类type实例化产生所定义的新类
 
# 使用class关键字创建
class PeaShooter(object):
    owner = "戴夫"
    def __init__(self, name):
        self.name = name
    def introduce(self):
        print(f"I`m {self.name}")
print(PeaShooter.__dict__)
# {'__module__': '__main__', 'owner': '戴夫', '__init__': <function PeaShooter.__init__ at 0x000002534E353AC0>, 'introduce': <function PeaShooter.introduce at 0x000002534E639EA0>, '__dict__': <attribute '__dict__' of 'PeaShooter' objects>, '__weakref__': <attribute '__weakref__' of 'PeaShooter' objects>, '__doc__': None}
# 使用type函数创建
# 类名
class_name = "PeaShooter"
# 基类
class_bases = (object,)
# 类的名称空间
class_dict = {}
class_body = """
owner = "戴夫"
def __init__(self, name):
    self.name = name
def introduce(self):
    print(f"T`m {self.name}")    
"""
# 类的属性和方法存储在class_dict里面. 第二个参数{}控制代码的执行环境
exec(class_body, {}, class_dict)
# 使用type函数创建
PeaSHooter = type(class_name, class_bases, class_dict)
print(PeaSHooter.__dict__)
# {'owner': '戴夫', '__init__': <function __init__ at 0x0000017E39653E20>, 'introduce': <function introduce at 0x0000017E39783AC0>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'PeaShooter' objects>, '__weakref__': <attribute '__weakref__' of 'PeaShooter' objects>, '__doc__': None}
【3】如何自定义元类
- 元类的作用是控制类的创建 
  - 也就意味着我们可以定制自己的类的具体行为
 
- class机制默认的元类是type(metaclass=type)- 也就是说我们修改metaclass参数就可以自定义元类
 
- 元类是一切类的基石,所以我们自定义的元类就必须继承type- 目的在于使用元类的大部分功能,仅定制我们需要的那部分功能
 
# 自定义元类
class MyMeta(type):
    pass
# 普通的类,元类默认是type
class PeaShooter(object, metaclass=type):
    pass
# 普通自定义的类,元类是Shooter
class PeaShooter(object, metaclass=Mymeta):
    pass
【二】自定义元类控制自定义类
【1】实例化对象的本质
- 我们知道类的实例化会触发魔法方法__new__、__init__- __new__:申请空间,创建并返回一个空对象
- __init__:接收new创建的空对象,并初始化这个空对象
 
- 我们知道实例的调用会触发魔法方法__call__- __call__:当对象被调用时触发
 
【2】控制自定义类的类名、继承关系、名称空间
(1)讲解
# 类名
class_name = "PeaShooter"
# 类的基类
class_bases = (object, )
# 类的名称空间
class_dict = {}
# 使用元类创建类
PeaShooter1 = type(class_name, class_bases, class_dict)
# 使用自定义元类创建类
class MyMeta(type):
    pass
PeaShooter2 = MyMeta(class_name, class_bases, class_dict)
- type是Python的内置元类,而使用type创建类就是通过元类type进行类的实例化过程,即元类的实例化会自动触发魔法方法- __init__、- __new__
- 使用元类type或者自定义元类MyMeta需要传入参数类名class_name、基类class_bases、名称空间class_dict
- 所以可以通过自定义的元类中的魔法方法__init__、__new__来控制类名、基类、名称空间这些参数
- 让我们来看看代码的执行执行结果
class MyMeta(type):
    def __init__(cls, what, bases, dict):
        print("自定义元类的魔法方法init")
        print(f"参数cls:{cls}")
        print(f"参数what:{what}")
        print(f"参数bases:{bases}")
        print(f"参数dict:{dict}")
        super().__init__(what, bases, dict)
    def __new__(cls, *args, **kwargs):
        print("自定义元类的魔法方法new")
        print(f"参数cls:{cls}")
        print(f"参数args:{args}")
        print(f"参数kwargs:{kwargs}")
        return super().__new__(cls, *args, **kwargs)
# 等价于 PeaShooter = MyMeta("PeaShooter", (object, ), {})
class PeaShooter(object, metaclass=MyMeta):
    pass
# 自定义元类的魔法方法new
# 参数cls:<class '__main__.MyMeta'>
# 参数args:('PeaShooter', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'PeaShooter'})
# 参数kwargs:{}
# 自定义元类的魔法方法init
# 参数cls:<class '__main__.PeaShooter'>
# 参数what:PeaShooter
# 参数bases:(<class 'object'>,)
# 参数dict:{'__module__': '__main__', '__qualname__': 'PeaShooter'}
(2)应用
-  要求:自定义元类来控制创建新类 -  类名必须含有射手(Shooter) 
-  类名首字母大写 
-  如果没有基类默认为object 
-  必须要有注释文档 
 
-  
-  小坑:不可以直接使用istitle()判断首字母大写,因为这个函数会要求其他字母都是小写 
class MyMeta(type):
    def __init__(cls, class_name:str, class_bases, class_dict):
        if "Shooter" not in class_name:
            raise NameError("必须含有Shooter")
        elif not class_name[0].istitle():
            print(class_name, type(class_name))
            raise NameError("必须首字母大写")
        elif not cls.__doc__:
            raise ValueError("类必须有注释文档")
        if not class_bases:
            class_bases = (object, )
        super().__init__(class_name, class_bases, class_dict)
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)
class iceShoot(metaclass=MyMeta):
    pass
# NameError: 必须含有Shooter
class iceShooter(metaclass=MyMeta):
    pass
# NameError: 必须首字母大写
class IceShooter(metaclass=MyMeta):
    pass
# ValueError: 类必须有注释文档
class IceShooter(metaclass=MyMeta):
    """
    这是寒冰射手
    """
    pass
print(IceShooter.__mro__)
# (<class '__main__.IceShooter'>, <class 'object'>)
【3】控制自定义类实例化的括号内容
(1)讲解
class MyMeta(type):
    def __call__(self, *args, **kwargs):
        print("这是自定义元类的魔法方法call")
        print(f"参数self:{self}")
        print(f"参数args:{args}")
        print(f"参数kwargs:{kwargs}")
        return super().__call__(*args, **kwargs)
class A(metaclass=MyMeta):
    def __init__(self, *args, **kwargs):
        print("这是根据自定义元类创建新类的魔法方法init")
        print(f"参数self:{self}")
        print(f"参数args:{args}")
        print(f"参数kwargs:{kwargs}")
    def __call__(self, *args, **kwargs):
        print("这是根据自定义元类创建新类的魔法方法call")
        print(f"参数self:{self}")
        print(f"参数args:{args}")
        print(f"参数kwargs:{kwargs}")
a = A("bruce", age=18)
a("tom", age=16)
# 这是自定义元类的魔法方法call
# 参数self:<class '__main__.A'>
# 参数args:('bruce',)
# 参数kwargs:{'age': 18}
# 这是根据自定义元类创建新类的魔法方法init
# 参数self:<__main__.A object at 0x00000200C1787DF0>
# 参数args:('bruce',)
# 参数kwargs:{'age': 18}
# 这是根据自定义元类创建新类的魔法方法call
# 参数self:<__main__.A object at 0x00000200C1787DF0>
# 参数args:('tom',)
# 参数kwargs:{'age': 16}
- 我们来看看这个例子:
- a = A("bruce", age=18)- 自定义的类(A)是根据自定义的元类(MyMeta)创建出来的
- 所以这里会执行自定义元类(MyMeta)的魔法方法__call__
- 然后将参数传递给自定义类的魔法方法___init__中
- 这两部内容是一样的,并且必须要有返回值,即将生成的类返回给实例a
- 综上所述:我们可以通过自定义元类(MyMeta)的魔法方法__call__来控制自定义类的括号内容
 
- a("tom", age=16)- 实例a使用括号调用了自定义类中的__call__方法,这是显而易见的,不需要解释
 
- 实例a使用括号调用了自定义类中的
(2)应用
- 要求:自定义元类来控制创建新类 
  - 实例化的参数必须通过关键字参数传参
- 不能通过位置关键字传参
 
class MyMeta(type):
    def __call__(cls, *args, **kwargs):
        if len(args):
            raise TypeError("实例化必须通过关键字传参")
        return super().__call__(*args, **kwargs)
        
class Student(metaclass=MyMeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
student_one = Student("bruce", 18)
# TypeError: 实例化必须通过关键字传参
student_two = Student(name="tom", age=22)
print(student_two.name, student_two.age)
# tom 22
【三】总结




















