元类
【一】概要
- 元类是类的类,它用于定义其他类。在Python中,一切皆对象,包括类。而元类就是用来创建这些类的“类”。
- 类是对象: 在Python中,类本身也是一个对象,而元类就是用来创建这些类的对象。
【二】常见用法
type
函数:在Python中,type
函数不仅可以返回对象的类型,还可以用于创建新的类型。当 type
用于创建新的类型时,它的参数是类的名称、基类元组和类的字典。
| """ |
| type(object) -> the object's type |
| type(name, bases, dict, **kwds) -> a new type |
| """ |
| |
| print(type(int)) |
| |
| class Foo(object): |
| pass |
| print(type(Foo)) |
| |
| |
| new_class = type('new_class', (object,), {}) |
| obj = new_class() |
| print(obj) |
| |
- 自定义元类:可以创建自定义的元类,通过继承
type
类并重写其方法来控制类的创建过程。这样,您可以在类被创建时执行额外的逻辑。
| |
| class MyMeta(type): |
| |
| def __new__(cls, name, bases, kwargs): |
| '''添加代码,当子类实例化时自动实现''' |
| print("将会自动执行此处内容") |
| return super().__new__(cls, name, bases, kwargs) |
| |
| class Son(metaclass=MyMeta): |
| |
| 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)) |
| |
| print(NewClass.__bases__) |
| print(NewClass.__dict__) |
| |
| |
| |
| '''不填参数将默认将第一个作为对象,返回这个对象的类,如果需要生成类,就必须传后续的参数,哪怕为空''' |
| new_class_two = type('new_class') |
| print(new_class_two) |
| print(new_class_two.__bases__) |
| print(new_class_two.__dict__) |
【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】通过元类定制化类的推导
- 对象是如何产生的? 调用类然后执行类里面的
__init__
方法了
- 类是如何产生的? 推导应该是,造出类的类里面的
__init__
方法,而这个类恰好是type元类
- 得出结论:如果想定制化类的代码,应该写在元类的
__init__
方法
- 元类是python自带的源码,我们是无法直接修改源码的,而我们又想使用元类中的方法,应该如何?
- 可以想到,派生!重用元类的
__init__
方法,并派生出自己的属性与方法
| |
| class MyMeta(type): |
| |
| 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): |
| 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 |
| |
| |
| |
| |
【2.1.1】定制类必须首字母大写
| |
| class MyMeta(type): |
| |
| 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): |
| 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() |
| |
| |
| |
| '''元类与正常继承的__init__对比''' |
| class Foo(object): |
| def __init__(self): |
| print("Foo的__init__") |
| |
| class Son(Foo): |
| def __init__(self): |
| print("Son的__init__") |
| super().__init__() |
| |
| s = Son() |
| |
| |
【3】通过元类定制类的对象的产生(__call__
方法)
【3.1】通过元类定制化类的对象推导
- 如何定制类的对象?通过实例化后,类中的
__init__
方法
- 类实例化时是通过
类名+ ()
进行实例化对象
类名+ ()
会触发类中的__call__
方法(如果类中定义了),如果没有就去父类或基类中查找__call__
方法
- 前面提到,其实类也是元类实例化出来的对象,所以
类名+ ()
也会触发元类中的__call__
方法
- 得出结论,如果需要定制实例化对象,那么应该写在元类的
__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) |
| |
| |
【补】同样的,在对象实例化时,会自动将参数传给__call__(*args,**kwargs)
| class MyMeta(type): |
| def __call__(self, *args, **kwargs): |
| print(args) |
| 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: |
| raise Exception("必须通过关键字传参!") |
| return super().__call__(*args, **kwargs) |
| |
| |
| class Son(metaclass=MyMeta): |
| def __init__(self, name, age): |
| print("Son.__init__") |
| |
| |
| |
| 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): |
| |
| |
| 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__) |
| |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库