Python面向对象之元类

元类

【一】概要

  • 元类是类的类,它用于定义其他类。在Python中,一切皆对象,包括类。而元类就是用来创建这些类的“类”。
  • 类是对象: 在Python中,类本身也是一个对象,而元类就是用来创建这些类的对象。

【二】常见用法

  • type函数:在Python中,type函数不仅可以返回对象的类型,还可以用于创建新的类型。当 type 用于创建新的类型时,它的参数是类的名称、基类元组和类的字典。
"""
    type(object) -> the object's type
    type(name, bases, dict, **kwds) -> a new type
"""

print(type(int))  # <class 'type'>

class Foo(object):
    pass
print(type(Foo))  # <class 'type'>


new_class = type('new_class', (object,), {})
obj = new_class()  # <__main__.new_class object at 0x000001A30A239750>
print(obj)

  • 自定义元类:可以创建自定义的元类,通过继承 type 类并重写其方法来控制类的创建过程。这样,您可以在类被创建时执行额外的逻辑。
# 自定义元类,继承type
class MyMeta(type):
    # 派生
    def __new__(cls, name, bases, kwargs):
        '''添加代码,当子类实例化时自动实现'''
        print("将会自动执行此处内容")
        return super().__new__(cls, name, bases, kwargs)

class Son(metaclass=MyMeta):
    # 元类不可直接实例化,且继承元类必须使用(metaclass=Meta)
    pass


s = Son()
# 将会自动执行此处内容

【三】详解

【1】type函数的参数

  • type(name, bases, dict) -> a new type
    • name:类名 (我是谁)
    • bases:父类(我来自哪里?我的父亲是谁?)
    • dict:类的名称空间(我有什么?)
'''dict参数需要放字典,可以通过dict()强转生成字典'''
'''或者自行写入字典'''
NewClass = type('NewClass', (object,), dict(name='user', age=18))
# NewClass = type('NewClass', (), {'name':'user','age':18})
print(NewClass.__bases__)  # (<class 'object'>,)  # bases 如果是空元组,那么默认继承object
print(NewClass.__dict__)
# {'name': 'user', 'age': 18, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'new_class' objects>,...}


'''不填参数将默认将第一个作为对象,返回这个对象的类,如果需要生成类,就必须传后续的参数,哪怕为空'''
new_class_two = type('new_class')   # 如果不填其他参数,将默认这个字符串为对象,返回的值为str
print(new_class_two)   # <class 'str'>
print(new_class_two.__bases__)  # (<class 'object'>,)
print(new_class_two.__dict__)   # str的名称空间,巨长,就不贴了

【1.1】type函数,相当于class

new_class = type('NewClass', (object,), dict(name='user', age=18))

'''相当于'''
class NewClass(object):
    name = 'user'
    age = 18

【2】通过元类定制化类(__init__方法)

【2.1】通过元类定制化类的推导

  1. 对象是如何产生的? 调用类然后执行类里面的__init__方法了
  2. 类是如何产生的? 推导应该是,造出类的类里面的__init__方法,而这个类恰好是type元类
  3. 得出结论:如果想定制化类的代码,应该写在元类的__init__方法
  • 元类是python自带的源码,我们是无法直接修改源码的,而我们又想使用元类中的方法,应该如何?
  • 可以想到,派生!重用元类的__init__方法,并派生出自己的属性与方法
# MyMeta继承元类
class MyMeta(type):
    # 派生出自己的__init__
    def __init__(self, name, bases, dict):
        '''写条件'''
        if name != 'Son':
            raise ValueError("只有Son可以继承我")
        super().__init__(name, bases, dict)

class Son(metaclass=MyMeta):
    pass
class Daughter(metaclass=MyMeta):  # ValueError: 只有Son可以继承我
    pass

s = Son()

【补】__init__(name,bases,dict)的三个参数,当类通过元类实例化时会自动传入值

class MyMeta(type):
    def __init__(self, name, bases, dict):
        print(f"类名:{name}")
        print(f"父类:{bases}")
        print(f"名称空间:{dict}")
        super().__init__(name, bases, dict)


class Son(metaclass=MyMeta):
    name = 'user'
    pass
# 类名:Son
# 父类:()
# 名称空间:{'__module__': '__main__', '__qualname__': 'Son', 'name': 'user'}

【2.1.1】定制类必须首字母大写
# MyMeta继承元类
class MyMeta(type):
    # 派生出自己的__init__
    def __init__(self, name, bases, dict):
        '''写条件'''
        if not name.istitle():
            raise ValueError("首字母必须大写!")
        super().__init__(name, bases, dict)


class Son(metaclass=MyMeta):
    pass


class son(metaclass=MyMeta):   # ValueError: 首字母必须大写!
    pass

【2.2】__init__的执行顺序:先执行元类的__init__,再执行自己的

class MyMeta(type):
    def __init__(self, name, bases, dict):
        print("MyMeta的__init__")
        super().__init__(name, bases, dict)


class Son(metaclass=MyMeta):
    def __init__(self):
        print("Son的__init__")


s = Son()
# MyMeta的__init__   # 优先执行元类中的方法
# Son的__init__

'''元类与正常继承的__init__对比'''
class Foo(object):
    def __init__(self):
        print("Foo的__init__")

class Son(Foo):
    def __init__(self):
        print("Son的__init__")
        super().__init__()

s = Son()
# Son的__init__   # 普通的继承时,将先执行自己的,再执行父类的
# Foo的__init__

【3】通过元类定制类的对象的产生(__call__方法)

【3.1】通过元类定制化类的对象推导

  1. 如何定制类的对象?通过实例化后,类中的__init__方法
  2. 类实例化时是通过类名+ ()进行实例化对象
  3. 类名+ ()会触发类中的__call__方法(如果类中定义了),如果没有就去父类或基类中查找__call__方法
  4. 前面提到,其实类也是元类实例化出来的对象,所以类名+ ()也会触发元类中的__call__方法
  5. 得出结论,如果需要定制实例化对象,那么应该写在元类的__call__方法
  • 同样的,因为是源码,无法修改,所以使用派生
class MyMeta(type):
    def __call__(self, *args, **kwargs):
        print("MyMeta.__call__")
        return super().__call__(*args, **kwargs)


class Son(metaclass=MyMeta):
    def __init__(self, name, age):
        print("Son.__init__")


s = Son('user', 18)
# MyMeta.__call__
# Son.__init__

【补】同样的,在对象实例化时,会自动将参数传给__call__(*args,**kwargs)

class MyMeta(type):
    def __call__(self, *args, **kwargs):
        print(args)  # ('user', 18)
        print(kwargs)  # {}
        print("MyMeta.__call__")
        return super().__call__(*args, **kwargs)


class Son(metaclass=MyMeta):
    def __init__(self, name, age):
        print("Son.__init__")


s = Son('user', 18)

【3.2】定制类的对象必须通过关键字传参

class MyMeta(type):
    def __call__(self, *args, **kwargs):
        if args:  # 如果args中有值,为True时,说明不符合条件
            raise Exception("必须通过关键字传参!")
        return super().__call__(*args, **kwargs)


class Son(metaclass=MyMeta):
    def __init__(self, name, age):
        print("Son.__init__")


# s = Son('user', 18)  # Exception: 必须通过关键字传参!
s = Son(name='user', age=18)

【4】小练习

【4.1】自动为类添加方法(自动打招呼)

class MyMeta(type):
    def __init__(cls, name, bases, cls_dict):
        if not name.istitle():
            raise Exception("类名必须首字母大写!")
        super().__init__(name, bases, cls_dict)

    def __new__(cls, name, bases, cls_dict):
        # 将功能添加到类的名称空间中
        # 传入lambda匿名函数
        cls_dict['say'] = lambda self, saying=name: print(f"Hello, {saying}! I'm Meta!")
        return super().__new__(cls, name, bases, cls_dict)


class F1(metaclass=MyMeta):
    pass


f = F1()
f.say()
print(F1.__dict__)
# {'__module__': '__main__', 'say': <function MyMeta.__new__.<locals>.<lambda> at 0x000001CD563C1A20>,...}
posted @ 2024-01-17 17:38  Lea4ning  阅读(56)  评论(1编辑  收藏  举报