元类
阅读此篇博文的顺序: 类定义阶段 - 类的实例化 - 元类 - 示例(源码) , 剩下的作为补充. 这样可以更顺畅的理解元类.
因为 type和object
、__call__
这两部分的内容自己当时总结的有些许生涩难懂..复习时也想了好一会.. 不是特别重要,了解即可!”
别较真! 深究就涉及C语言结构体了. 在此阶段, 我们只需要明白:
object是所有类的基类, 其他所有的类都是由type创建的. 想要自定义创建, 得基于metaclass指定一个类, 该类继承type!!
元类是做什么的? 它是用来控制我们类的生成过程的, 默认情况下, 我们自定义的类都是由type创建的.
# 类定义阶段
定义类 类似于 `import 模块名`
导入模块会创建一个namescope. 通过 模块名.属性名 从namescope中取属性/变量.
同理! 从上往下运行到class定义的类代码,会立刻开辟一个类的namescope,将类中的变量和方法/函数往namescope中丢.
★ (类中的代码/类体代码 在定义阶段就执行啦!!)
★ 记住: "执行 py文件、执行导入模块、类定义、类的实例化/类的调用、函数调用 的代码时,会开辟namescope."
特别注意,类中函数体的代码要在函数被调用时才会执行!
---------
| 数据属性 |
类名 -->| |
| 函数属性 |
--------- 类名指向类的namescope
查看类的namescope: 类名.__dict__
类名.属性名 等同于 类名.__dict__["属性名"]
★ So,对namescope的CURD的本质就是在操作字典!!
★ (类的实例是一样的! 查看实例的namescope: 实例名.__dict__)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 类的实例化
类在实例化的时候会自动调用
__init__
, 但其实在调用__init__
之前会先自动调用__new__
!
# 基本使用
__new__
: 为类实例化对象申请'开辟'一片内存 / 创建实例对象的;
__init__
: 为类实例化对象设置独有的属性 / 为实例对象绑定属性;
class A:
# ★!!注意:该方法就是一个普通方法 print(A.__new__) --> <function A.__new__ at 0x7f9b6ba068b0>
# 只不过会类实例化时会自动调用,并将当前类作为cls的实参传入.
def __new__(cls, *args, **kwargs):
print("__new__")
# !!!这里的参数cls就表示A这个类本身!!! ★ 若class B(A):pass,那么B()时,此处的cls就是<class '__main__.B'>
print(cls) # <class '__main__.A'>
# object.__new__(cls) 便是根据cls创建cls的实例对象
return object.__new__(cls)
# 然后执行__init__, 里面的self指的就是实例对象
# 在执行__init__的时候, __new__的返回值会自动作为参数传递给这里的self
def __init__(self, *args, **kwargs):
print("__init__")
A()
"""
__new__
<class '__main__.A'>
__init__
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 重要结论
★ 几个比较重要的结论!
1> **__new__
中必须将类A的实例对象返回, 才会执行__init__
, 并且执行的时候会自动将__new__
的返回值作为参数传给self. **
2> 一个对象是什么, 取决于其类型对象的__new__
返回了什么.
3>__new__
里面的参数一定要和__init__
是匹配的, 除了第一个参数之外.
参考文档: https://www.cnblogs.com/traditional/p/13593927.html
很佩服这位老哥,写得很棒!!
下面将用三段代码验证上述的三个结论!!
结论1的验证
class A:
def __new__(cls, *args, **kwargs):
print("__new__")
def __init__(self):
print("__init__")
A() # __new__
# -- 我们看到只有__new__被调用了,__init__则没有! 【结论1的验证】
2
3
4
5
6
7
8
9
10
11
结论1和结论2的验证
class A:
def __new__(cls, *args, **kwargs):
print("__new__")
# -- 这里必须返回A的实例对象, 否则__init__函数是不会执行的 【结论1的验证】
return 123
def __init__(self):
print("__init__")
a = A()
print(a + 1)
"""
__new__
124
"""
# -- 我们看到A在实例化之后得到的是一个整型, 原因就是__new__返回了123 【结论2的验证】
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
结论3的验证.
object.__new__(cls)
、__new__
接收的name、__new__
接收的age 会组合起来, 分别传给__init__
的 self、name、age!!!
class A:
# __new__里面的参数一定要和__init__是匹配的, 除了第一个参数之外 【结论3的验证】
def __new__(cls, name, age):
return object.__new__(cls)
def __init__(self, name, age):
self.name = name
self.age = age
# A("夏色祭", -1) 这里传入了两个参数, 那么: A、"夏色祭"、-1 就会组合起来, 分别传给__new__的 cls、name、age
# 然后__new__里面返回了一个实例对象
# ★★ 那么:
# object.__new__(cls)创建的实例、__new__接收的name、__new__接收的age 会组合起来, 分别传给__init__的 self、name、age
a = A("夏色祭", -1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# type和object
object是父子关系的顶端, 所有的数据类型的mro继承链的 继承关系 最后都是object; (type也会继承object)
type是类型 实例关系 的顶端. (object也是type的一个实例)
object提供基础的功能, type用于创建“类”. “type”由谁创建的呢? 别纠结了, 再深究就是C语言的结构体啦!!在python语法层面是解释不了的.
# 专业术语
首先我们这里把python中的对象分为三种:
1> 内建对象: Python中的内建对象、或者叫内置对象, 比如int、str、list、type、object等等;
2> class对象: 程序员通过Python中的class关键字定义的类. 当然我们往往也会把内建对象和class对象统称为类对象;
3> 实例对象: 由类对象(内建对象或者class对象)创建的实例.
而对象之间存在着以下两种关系:
1> is-kind-of: 对应面向对象理论中父类和子类之间的关系; -- 继承关系.
2> is-instance-of: 对应面向对象理论中类和实例之间的关系; -- 实例关系.
# 内置方法
继承关系
__bases__
: 查看一个 类型/类 的所有父类;
__base__
: 查看一个 类型/类 的继承的第一个类;
issubclass(sub, super)
检查sub类是否是 super类的 子类, 是, 返回True.
注意: 在python3中统一了类与类型的概念, 类就是类型.. (详见: python面向对象/0_OOP基本/类就是类型 部分的笔记)
实例关系
__class__
: 查看一个实例的类型;
type
: 也可以查看一个实例的类型; (即该实例是由谁实例化得到的)
isinstance(obj,cls)
: 返回True或False. 若想返回True,满足下面两个条件其中一个即可.
1> 检查obj是否是 类cls或类cls的子类 的一个实例, 是, 返回True;
2> type(obj)的类型 是否跟 cls 一致, 是, 返回True.
这里的obj可以是类对象、实例对象.
class A(object):
pass
class B(A):
pass
if __name__ == '__main__':
print(type(B)) # <class 'type'>
print(B.mro()) # [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
print(B.__base__) # <class '__main__.A'>
print(B.__bases__) # (<class '__main__.A'>,)
print(issubclass(B, object)) # True
print(issubclass(B, type)) # False
# 细品!!
# isinstance(obj,cls) 检查obj是否是 <类cls或类cls的子类> 的一个实例, 是, 返回True;
# 这里的obj可以是类对象、实例对象.
# - obj是实例对象
b = B()
print(isinstance(b, B)) # True
print(isinstance(b, A)) # True
print(isinstance(b, object)) # True
print(isinstance(b, type)) # False
# - obj是类对象
print(isinstance(B, type)) # True -- 类默认都是type实例化得到的
print(isinstance(B, object)) # True -- 因为type的父类是object
print(isinstance(B, A)) # False
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 验证关系
type实例关系的老大, object继承关系的老大!!
实线表示继承关系 该类父类是谁/该类是谁的子类; 虚线表示实例关系 该对象是通过谁实例化得到的/该对象是什么类型..
(类就是类型, python处处皆对象 So, 图中的<type 'list'>
既表示它是list类型,也表示它是list类.. 当然它也是一个对象! )
虚线是跨列产生关系, 而实线只能在一列内产生关系. 除了type和object两者外.
验证上图中所画的关系:
>>> object
<class 'object'>
>>> type
<class 'type'>
>>> object.__bases__ # object 无父类,因为它是链条顶端
()
>>> type.__bases__ # type是object的子类
(<class 'object'>,)
>>> type(object) # object的类型是type
<class 'type'>
>>> type(type) # type的类型是自己
<class 'type'>
# -- list, dict, tuple 这些内置数据类型,他们的父类都是object, 类型都是type/都是由type实例化得到的.
>>> list.__bases__
(<class 'object'>,)
>>> list.__class__
<class 'type'>
# -- list实例化得到了mylist对象,该对象类型是<type 'list'>, 但该对象没有父类
>>> mylist = [1,2,3]
>>> mylist.__bases__
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
mylist.__bases__
AttributeError: 'list' object has no attribute '__bases__'
>>> mylist.__class__
<class 'list'>
# -- 自定义的类
>>> c = C()
>>> C.__bases__
(<class 'object'>,)
>>> C.__class__
<class 'type'>
>>> c.__bases__
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
c.__bases__
AttributeError: 'C' object has no attribute '__bases__'
>>> c.__class__
<class '__main__.C'>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# __call__
一个对象能否被调用, 取决于它的类型对象中是否定义了
__call__
函数
# 基本使用
若想让类实例化对象可以被调用, 则需要在类中定义
__call__
方法!
class Foo:
def __call__(self, *args, **kwargs):
print(self) # <__main__.Foo object at 0x7f99cc986760>
print(args) # (1, 2, 3)
print(kwargs) # {'x': 4, 'y': 5}
return "Hello"
print(Foo) # <class '__main__.Foo'>
obj = Foo()
print(obj) # <__main__.Foo object at 0x7f99cc986760>
# -- 要想让obj这个类实例化对象变成一个可调用的对象,需要在该对象的类中定义一个__call__方法
# 该实例方法会在调用实例化对象obj时自动触发,调用obj的返回值就是__call__方法的返回值
print(obj(1, 2, 3, x=4, y=5)) # Hello -- obj的调用会触发Foo中__call__方法的执行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 深入探究
>>> a = 1
>>> a()
Traceback (most recent call last):
File "<pyshell#20>", line 1, in <module>
a()
TypeError: 'int' object is not callable
>>> print(hasattr(int, "__call__"))
True
2
3
4
5
6
7
8
9
上述代码运行结果报错, 整数对象是不可调用的,这显然意味着int这个类里面没有__call__
函数.
但我们通过反射打印 print(hasattr(int, "__call__"))
的结果为True.. 这是为何?
因为hasattr反射和类属性查找的规则是一样的.
So, int的类型是type, 而type里面有__call__
, 因此即便int里面没有,hasattr(int, "__call__")
依旧是True
内心OS: 以往我们的记忆都是, hasattr、类属性、实例属性的查找都是基于继承链的, 大体没错, 这里做一点补充!!
class A:
foo = "in A"
class B(A):
temp = "in B"
if __name__ == '__main__':
# ▲ 类属性查找顺序/查找类是否具有某个属性:
# 类-父类-...-顶级父类object- 若继承链中没有,还会自动到对应的类型对象(即谁实例化出的该类,此处是type)里面去找
print(hasattr(B, "temp"))
print(hasattr(B, "foo"))
print(hasattr(B, "__call__"))
print(hasattr(type, "__call__"))
# ▲ 实例属性查找顺序:
# 自身-类-父类-...-顶级父类object - 若继承链中没有,就真没有了.
# ps: object内部没有__call__函数
print(hasattr(B(), "temp"))
print(hasattr(B(), "foo"))
print(hasattr(B(), "__call__")) # ★★★ 继承链中没有,就不会去元类中找啦!!
"""
True
True
True
True
True
True
False
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
一丢丢题外话.
所有的类都是可以调用的,因为type是它们的类型对象,而type内部是有__call__函数的.
但是默认情况下实例对象是不可调用的.
实例对象加括号执行时,会去找__call__方法!
如果实例对象的类型对象、以及该类型对象所继承的类中没有定义__call__函数的话,会继续沿着继承链挨个进行搜索
直到搜索到object时发现还没有__call__函数的话,那么就报错了.
所以一个整数对象是不可调用的,而整数类型是可调用的,我们发现这并不是在编译的时候就能够检测出来的错误,而是在运行时才能检测出来!
2
3
4
5
6
7
# 元类
我们一步步由浅到深的分析.. 只分析drf序列器源码中所需要用到的知识点.
# 创建类
- type可以创建类.默认. class type: def __new__(): 创建类/开辟一个类的空间 def __init__(): 给创建的类进行初始化/往类空间放一些东西 # ★ 类加括号实例化,会先调用类中的new,再调用init Foo = type("Foo", (object,), {'v1': 123, 'func': lambda self: 999}) - 自定义类来创建类,但该自定义类需继承type类.因为得将type类的所有功能拿过来,在它基础上进行扩展定制. class MyType(type): # 重写了type类中的__new__方法.会优先调用这里的new,有很多扩展空间,此处该方法最后用了super. def __new__(): super().__new__()
1
2
3
4
5
6
7
8
9
10
11
12
13
创建类: 方式一 通过class关键字 "本质上就是通过type创建的"
class Foo(object):
v1 = 123
def func(self):
return 999
2
3
4
5
创建类: 方式二 通过type类 等同于 方式一
# type要么接收一个参数,要么接收三个参数.接收一个参数查看类型,接受三个参数创建一个类.
# 类名 = type("类名",(父类,),{成员})
Foo = type("Foo", (object,), {'v1': 123, 'func': lambda self: 999})
print(Foo) # <class '__main__.Foo'>
print(Foo.__name__) # Foo
print(Foo.__bases__) # (<class 'object'>,)
print(Foo.v1) # 123
2
3
4
5
6
7
基于自定义类MyType创建类: 方式一
class MyType(type):
def __new__(mcs, *args, **kwargs):
xx = super().__new__(mcs, *args, **kwargs)
return xx # <class '__main__.Foo'> -- xx是创建的类
Foo = MyType("Foo", (object,), {'v1': 123, 'func': lambda self: 999})
2
3
4
5
6
基于自定义类MyType创建类: 方式二
class MyType(type):
def __new__(mcs, *args, **kwargs):
"""
mcs <class '__main__.MyType'>
args ('Foo', (<class 'object'>,), {'v1': 123, 'func': <function <lambda> at 0x7fdcd35bb310>})
kwargs {}
"""
xx = super().__new__(mcs, *args, **kwargs)
return xx
class Foo(object, metaclass=MyType):
v1 = 123
def func(self):
return 999
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 参数和返回值
元类和类之间的关系 和 类与实例对象的关系, 之间是很相似的, 完全可以把类对象看成是元类的实例对象.
Foo既然指定了metaclass为MyType, 表示Foo这个类由MyType创建, 那么MyType的__new__
函数返回了什么, Foo就是什么!
class MyType(type):
"""
我们看到一个类在创建的时候会向元类的__new__中传递三个值
分别是类名、继承的基类、类的属性
"""
def __new__(mcs, names, bases, attrs):
print("mcs:", mcs) # mcs: <class '__main__.MyType'> 当前类MyType!!
print("names:", names)
print("bases:", bases)
print("attrs:", attrs)
"""
Foo指定metaclass, 表示Foo这个类由MyType创建
在前面,我们说类实例化过程中,__new__是为实例对象开辟内存的,那么MyType的实例对象是谁呢? 显然就是这里的Foo
简而言之, 因为Foo指定了metaclass为MyType, 所以Foo的类型就是MyType
但在这个例子中,Foo并没有被创建出来!!为何?
在前面我们已经说了,__new__一定要将创建的实例对象返回才可以,这里的MyType是元类
类对象Foo等于MyType的实例对象, MyType的__new__就负责为类对象Foo分配空间
但显然我们这里并没有分配, 而且返回的还是一个None, 如果我们返回的是123, 那么print(Foo)就是123
因为元类MyType里的__new__方法返回的值是None,So,Foo的类型是<class 'NoneType'>
"""
class Foo(object, metaclass=MyType): # -- 指定元类MyType来创建Foo这个类
pass
if __name__ == '__main__':
print("===")
print(Foo)
print(type(Foo))
"""
mcs: <class '__main__.MyType'>
names: Foo
bases: (<class 'object'>,)
attrs: {'__module__': '__main__', '__qualname__': 'Foo'}
===
None
<class 'NoneType'>
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
创建类之前,可对类中的成员做点操作!!
class MyType(type):
def __new__(mcs, names, bases, attrs):
# ★ 创建类之前,可对类中的成员做点操作
del attrs['v1']
attrs['say'] = "Hello"
# 等价于return type.__new__(MyType, names, bases, attrs) 表示类由MyType创建
return super().__new__(mcs, names, bases, attrs) # 创建类
"""注意:
此处不能写成 type(name, bases, attr), 因为这样的话类还是由type创建的
这样想就解释通了(´▽`)
type(name, bases, attr) 等价于 type.__new__(type, name, bases, attr)
"""
class Foo(object, metaclass=MyType):
v1 = 123
def func(self):
return 999
if __name__ == '__main__':
print("===")
for item in ["v1", "say"]:
if hasattr(Foo, item):
print(item, ":>>", getattr(Foo, item))
"""
===
say :>> Hello
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 继承情况
一个类在没有指定的metaclass的时候, 如果它的父类指定了, 那么这个类的metaclass等于父类的metaclass!!
class MyType(type):
def __new__(mcs, name, bases, attr):
name = name * 2
return super().__new__(mcs, name, bases, attr)
class B(metaclass=MyType):
pass
class A(B):
pass
print(A.__class__) # <class '__main__.MyType'> 等同于 type(A)
print(A.__name__) # AA
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 扩展
上面阐述的有关元类的知识点, 用于看drf序列器的源码已经足够了.. 在这里, 再做一点元类知识的扩展.
class MyType(type):
def __new__(mcs, name, bases, attrs):
print("创建类.")
return super().__new__(mcs, name, bases, attrs)
class Base(object, metaclass=MyType):
def __new__(cls, *args, **kwargs):
print("创建类实例化对象.")
return object.__new__(cls)
def __init__(self, *args, **kwargs):
print("类实例化对象初始化.")
if __name__ == '__main__':
print("===")
obj = Base()
"""
创建类.
===
创建类实例化对象.
类实例化对象初始化.
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上述代码中 Base类实例化时,先new后init, 是如何触发的呢? 是由MyType中的call方法来触发的!!
★★★下面的程序能理清, 那元类就可以暂时毕业啦!! (⁎⁍̴̛ᴗ⁍̴̛⁎)
★注意思考清楚下述代码中的mcs、cls是啥! + 前面类的实例化小节那三条很重要的结论!!
class MyType(type):
def __new__(mcs, name, bases, attrs): # mcs --:> <class '__main__.MyType'>
print("创建类.")
return super().__new__(mcs, name, bases, attrs)
def __call__(cls, *args, **kwargs): # cls --:> <class '__main__.Base'>
# 结合类的实例化小节那三条很重要的结论的验证示例,就能很快明白此处的__call__方法为何要这样写!!
print("执行元类MyType的call方法.")
obj = cls.__new__(cls, *args, **kwargs)
print("---")
cls.__init__(obj, *args, **kwargs)
return obj
class Base(object, metaclass=MyType):
def __new__(cls, *args, **kwargs): # cls --:> <class '__main__.Base'>
print("创建类实例化对象.")
return object.__new__(cls)
def __init__(self, *args, **kwargs):
print("类实例化对象初始化.")
def __call__(self, *args, **kwargs):
return "Base.call"
if __name__ == '__main__':
print("===")
obj = Base()
print(obj())
"""
创建类.
===
执行元类MyType的call方法.
创建类实例化对象.
---
类实例化对象初始化.
Base.call
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Q: 思考个问题.为啥会触发元类MyType中的call方法呢?
A: 你这样想:
如果我说,类实例化得到的对象加括号会触发类中的call方法,相当于 类()(), 没问题吧?! 嗯.这是call方法的常识.
python处处皆对象.
代入元类中,MyType()类实例化得到 Base类这个对象, Base这个对象再加括号. 相当于 MyType()()
So, Base() 会触发MyType类中的call方法!!
当然Base类实例化创建的对象加括号就会触发Base类里的call方法. ok, 这个解释闭环啦.
# 示例(源码)
1> UserSerializer类根据继承关系是由元类SerializerMetaclass创建的.
2> list(attrs.items()) 是浅拷贝, 之所以拷贝, 是因为对迭代对象循环的同时剔除迭代对象里面的元素,会遗漏一些元素!!
class SerializerMetaclass(type):
def __new__(mcs, name, bases, attrs):
# 在创建类之前做了一点小操作,细品. -- attrs是name中的成员,此处的name是UserSerializer.
data_dict = {}
for k, v in list(attrs.items()):
if isinstance(v, int):
data_dict[k] = attrs.pop(k)
attrs['_declared_fields'] = data_dict
return super().__new__(mcs, name, bases, attrs)
class BaseSerializer(object):
pass
class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
pass
class ModelSerializer(Serializer):
pass
class UserSerializer(ModelSerializer):
v1 = 111
v2 = 222
v3 = "world"
if __name__ == '__main__':
print(UserSerializer._declared_fields) # {'v1': 111, 'v2': 222}
print(UserSerializer.v3) # world
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32