peewee

深入理解 Python 中的类与元类

最近在使用 peewee 来做数据库 ORM,期间遇到一些疑问便查了下源码,结果发现好多以前没有好好理解并整理的知识。 参考视频:https://www.bilibili.com/video/BV1uA411V7dW?p=1 关于 Python 中类的基本用法,参考以前写的文章 《python 小技巧与坑 - python 的类》 总结: 对象(obj)是类(class)的一个实例,而类(class)本身其实也是一个对象,它是元类(metaclass)的实例。 在 Python 中,默认情况下,所有类的元类都是 type,所有类的基类都是 object。 规定术语: 我们先来规定下几个术语在本文中的特定意思,以免混淆。 类对象,用来特指类本身这个对象,而不是由类生成的对象实例。 类实例,用来特指由类生成的对象实例。 类属性(类变量),指在类定义体内部,__init__() 方法之外,定义的属性。类属性可以通过 Class.attr 调用也可以通过 instance.attr 调用。 实例属性(实例变量),指在类的 __init__() 方法中,或类定义体外部,通过 instance.attr 定义的属性。实例属性只能通过 instance.attr 调用。 一、特殊函数与特殊属性 1.1 如何区分对象 is 操作符用来判断两个对象是否为同一个。 id() 函数返回对象的唯一标识,在 CPython 实现中,返回的就是该对象的内存地址。可以使用 hex(id(obj)) 将其转换成16进制。 type() 函数返回对象的类型。 1.2 可重写(override)的类方法 1.2.1 __new__(cls[, ...]) object.__new__(cls[, ...]) 方法用来创建 cls 类的对象实例。其中第一个参数 cls 代表当前类,其余参数应该与构造器表达式保持一致,或者使用可变参数囊括构造器表达式的参数(构造器表达式指 x = ClassName(args) 这种语法)。它的返回值应该是一个新的对象实例(cls 类的实例)。__new__() 实际上是一个静态方法,不过不需要加 @staticmethod 进行声明 当你使用构造器表达式 x = ClassName(args) 来创建对象的时候,Python 解释器会先调用 ClassName.__new__(cls) 创建一个对象实例,然后再对该对象调用 __init__(self[, ...]) 进行初始化。 如果 __new__() 方法没有返回一个对象实例,那么后续就不会调用 __init__(self[, ...]) 方法。 __new__(cls[, ...]) 的默认实现是调用父类的 __new__() 方法 spuer().__new__(cls, ),你可以在此之后对创建出来的对象实例进行修改。 1.2.2 __init__(self[, ...]) __init__(self[, ...]) 的调用发生在 __new__(cls) 方法创建完对象实例之后,该对象实例返回给用户之前。self 参数代表该对象实例,其余参数应该与构造器表达式保持一致,或者使用可变参数囊括构造器表达式的参数。__init__() 不需要有返回值,构造器表达式返回的对象实例是由 __new__() 生成的。 在派生类的 __init__() 方法中,必须主动调用父类的 __init__() 方法, super().__init__([args...]),以确保对象实例中父类部分初始化成功。 1.2.3 __del__(self) __del__() 方法会在对象实例即将销毁之前自动调用。注意 __del__() 方法实际上并不执行销毁对象、回收内存的操作,这些都是由 Python 垃圾回收机制完成的。所以即使你手动显式调用 x.__del__(),实际上只是运行了一次 __del__() 方法,并不会销毁对象,甚至连对象的引用计数都不会减少。 要想显式删除一个对象,需要使用 del 语句。del x 并不会直接调用 x.__del__(),而是将对象 x 的引用计数减1。当对象的引用计数为0的时候,Python 的 GC 机制会自动为其调用 __del__()。(很诡异,del 在 Python 中是一个 statement,而不是一个 function) 派生类的 __del__() 方法,必须主动调用父类的 __del__() 方法。 Python 并不保证在解释器退出时,会对所有还存活的对象调用 __del__() 方法。 1.2.4 __repr__(self) Python 的内建函数 repr(x) 会自动调用对象的 x.__repr__() 方法。 __repr__() 必须返回一个“正式的”字符串(相比于 __str__() 而言)。如果可以的话,返回的字符串应该是一个有效的 Python 表达式,能够用来重建对象实例;如果做不到的话,应该返回类似<...some useful description...> 这样格式的字符串。 1.2.5 __str__(self) Python 的内建函数 str(x), print(x) 会自动调用 x.__str__() 方法。该方法必须返回一个“非正式的”字符串,用来代表该对象实例。与 __repr__() 不同的是,__str__() 并不期望返回一个可重建对象的 Python 表达式字符串,可以更随意进行定制。 默认的__str__(self) 方法其实调用的就是 self.__repr__()。 1.2.6 __call__(self[, ...]) 当对象 x 被以函数形式 x() 使用的时候,就会自动调用 x.__call__() 方法(x.__call__() 等价于 Class_of_x.__call(x) )。 1.2.7 示例代码 class User(object): def __new__(cls, *args, **kwargs): # __new__ 方法用于创建 cls 类的对象 print('\n===== 调用 User.__new__() 方法') print('cls: ', cls) print('type(cls): ', type(cls)) print('args: ', args) print('kwargs: ', kwargs) obj = super().__new__(cls) print('id(obj): ', hex(id(obj))) return obj def __init__(self, name): # 对 self 这个对象进行初始化 print('\n===== 调用 User.__init__() 方法') print('id(self): ', hex(id(self))) super(User, self).__init__() self.name = name pass def __del__(self): # __del__() 方法并不能销毁对象,它只是在对象即将被…