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】通过元类定制化类的推导
- 对象是如何产生的? 调用类然后执行类里面的
__init__
方法了 - 类是如何产生的? 推导应该是,造出类的类里面的
__init__
方法,而这个类恰好是type元类 - 得出结论:如果想定制化类的代码,应该写在元类的
__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】通过元类定制化类的对象推导
- 如何定制类的对象?通过实例化后,类中的
__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)
# 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>,...}