[Python] 深入理解元类并区分元类中init、call、new方法
0. 参考书籍和元类的作用总结
本文内容参考书籍《流畅的Python》《Effective Python》《编写高质量代码:改善Python程序的91个建议》。我只是知识的搬运工,将知识进行整理,区分出其中的重点并加入自己的理解。感兴趣的最好去翻看原书的相关内容。
- 执行到类的代码体结束后时,会调用该类的元类中的
__new__
和__init__
方法,利用这两个方法,可以对类做一些定制化的操作。 - 初始化类的实例时,会调动该类的元类中的
__call__
方法,利用这个方法,可以对类的实例对象做一下定制化的操作。 - 初始化类的实例时,
__call__
、__new__
、__init__
三个方法的执行顺序是 元类的__call__
、类的__new__
、类的__init__
。
这三点参考 【6. 元类中的 init 、call、new 方法】,结合代码的执行顺序就可以理解了。
1. 元类的定义
元类是制造类的工厂,元类是用于构建类的类。 这句话很重要!!!这句话很重要!!!这句话很重要!!!
我们正常定义类是这样的:
class Person(object):
pass
class Child(Person):
ClassName = 'Child'
def __init__(self, name, age):
if age > 20:
raise ValueError("Child's age must small than 20")
self._name = name
self._age = age
def speak(self):
print(self._name, self._age)
我们还可以使用 type 来动态创建类:
class Person(object):
pass
ClassName = 'Child'
def __init__(self, name, age):
if age > 20:
raise ValueError("Child's age must small than 20")
self._name = name
self._age = age
def speak(self):
print(self._name, self._age)
# type 的三个参数分别是 name、bases 和 dict。最后一个参数是一个映射,指定新类的属性名和值。
Child = type('Child', (Person,), {
'ClassName': ClassName,
'__init__': __init__,
'speak': speak})
john = Child('John', 20)
john.speak()
print(Child.__dict__) # 有 ClassName,__init__ 和 speak 属性
使用 type 关键字去拼接函数和属性来创建类,实在是不够优雅。之所以谈到这个,是为了方便我们后面理解元类是如何动态改变类的属性的。
2. 区分继承自 type 和使用 metaclass 关键字
元类从 type 类继承了构建类的能力。所有类都直接或间接地是 type 的实例,不过只有元类同时也是 type 的子类。搞清楚这句话,意思就是,元类是 type 类的子类。使用 metaclass 关键字的类并不是type 的子类。
class ClassOne(type): # 这个是元类
pass
class ClassTwo(metaclass=type): # 不是元类,是用元类创建的类
pass
class ClassThree(object, metaclass=type): # 与ClassTwo一模一样。不是元类,是用元类创建的类
pass
class ClassFour(ClassOne): # 继承自元类,是元类
pass
print(ClassOne.__mro__)
print(ClassTwo.__mro__)
print(ClassThree.__mro__)
print(ClassFour.__mro__)
(<class '__main__.ClassOne'>, <class 'type'>, <class 'object'>)
(<class '__main__.ClassTwo'>, <class 'object'>)
(<class '__main__.ClassThree'>, <class 'object'>)
(<class '__main__.ClassFour'>, <class '__main__.ClassOne'>, <class 'type'>, <class 'object'>)
一定要搞清继承自 type 和使用 metaclass 关键字的不同。前者是元类,后者是由元类创建的类。
3. 类装饰器的运行
为什么要讲类装饰器?因为类装饰器能以较简单的方式做到需要使用元类去做的事情 ——创建类时定制类。
类装饰器与函数装饰器非常类似,是参数为类对象的函数,返回原来的类或修改后的类。我们先来看代码,你可以尝试写一写答案:
"&#