数据模型
数据模型其实是对 Python 框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括但不限于序列、迭代器、函数、类和上下文管理器
Python 解释器碰到特殊的句法时,会使用特殊方法(魔术方法是特殊方法的昵称)去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾(例如__getitem__
)。
比如obj[key]
的背后就是__getitem__
方法,为了能求得my_collection[key]
的值,解释器实际上会调用my_collection.__getitem__(key)
这些特殊方法名能让你自己的对象实现和支持以下的语言框架,并与之交互
- 迭代
- 集合类
- 属性访问
- 运算符重载
- 函数和方法的调用
- 对象的创建和销毁
- 字符串表示形式和格式化
- 管理上下文(即 with 块)
示例:一摞有序的纸牌
1 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 |
|
按照常规,用点数来判定扑克牌的大小,2 最小、A 最大;同时还要加上对花色的判定,黑桃最大、红桃次之、方块再次、梅花最小。下面就是按照这个规则给扑克牌排序的函数,梅花 2 的大小是 0,黑桃 A 是 51
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
一个简单的二维向量类
1 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 |
|
字符串表示形式
Python 有一个内置的函数叫 repr,它能把一个对象用字符串形式表达出来以便辨认,这就是“字符串表示形式”。repr 是通过 __repr__
这个魔术方法来得到一个对象的字符串表示形式的。
如果没有实现 __repr__
,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是<Vector object at 0x10e100070>
__repr__
和 __str__
的区别在于,后者是在 str()
函数被使用,或是在用 print
函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好
如果你只想实现这两个魔术方法中的一个,__repr__
是更好的选择,因为如果一个对象没有 __str__
函数,而 Python 又需要调用它的时候,解释器会用 __repr__
作为替代
算术运算符
通过 __add__
和 __mul__
,为向量类带来了 +
和 *
这两个算术运算符。值得注意的是,这两个方法的返回值都是新创建的向量对象,被操作的两个向量(self 或 other)还是原封不动,代码里只是读取了它们的值而已。中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值
自定义的布尔值
尽管 Python 里有 bool 类型,但实际上任何对象都可以用于需要布尔值的上下文中(比如 if 或 while 语句,或者 and、or 和 not 运算符)。为了判定一个值 x 为真还是为假,Python 会调用 bool(x),这个函数只能返回 True 或者 False
默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对 __bool__
或者 __len__
函数有自己的实现。bool(x)
背后是调用 x.__bool__()
的结果;如果不存在 __bool__
方法,那么 bool(x)
会尝试调用 x.__len__()
。若返回 0,则 bool 会返回 False;否则返回 True
魔术方法一览
跟运算符无关的魔术方法
类别 | 方法名 |
---|---|
字符串/字节序列表示形式 | __repr__、__str__、__format__、__bytes__ |
数值转换 | __abs__、__bool__、__complex__、__int__、__float__、__hash__、__index__ |
集合模拟 | __len__, __getitem__, __setitem__, __delitem__, __contains__ |
迭代枚举 | __iter__, __reversed__, __next__ |
可调用模拟 | __call__ |
上下文管理 | __enter__, __exit__ |
实例创建和销毁 | __new__, __init__, __del__ |
属性管理 | __getattr__, __getattribute__, __setattr__, __delattr__, __dir__ |
属性描述符 | __get__, __set__, __delete__ |
跟类相关的服务 | __prepare__, __instancecheck__, __subclasscheck__ |
跟运算符相关魔术方法(部分省略了双下划线,实际上都应该有)
类别 | 方法名和对应的运算符 |
---|---|
一元运算符 | __neg__ -, __pos__ +, __abs__ abs() |
众多比较运算符 | __lt__ <, __le__ <=, __eq__ ==, __ne__ !=, __gt__ >, __ge__ >= |
算术运算符 | __add__ +, sub -, mul *, truediv /, floordiv //, mod %, divmod divmod(), pow **或pow(), round round() |
反向算术运算符 | radd, rsub, rmul, rtruediv, rfloordiv, rmod, rdivmod, rpow |
增量赋值算术运算符 | iadd, isub, imul, itruediv, ifloordiv, imod, ipow |
位运算符 | invert ~, lshift <<, rshift >>, and &, or |, xor ^ |
反向位运算符 | rlshift, rrshift, rand, rxor, ror |
增量赋值位运算符 | ilshift, irshift, iand, ixor, ior |
当交换两个操作数的位置时,就会调用反向运算符(b * a
而不是a * b
)。
增量赋值运算符则是一种把中缀运算符变成赋值运算的捷径(a = a * b
就变成了a *= b
)
__new__
方法
在 Python 中__new__
方法与__init__
方法类似,但是如果两个都存在那么__new__
先执行。
Python 当中__init__
并不是构造函数,__new__
才是
在基础类object中,__new__被定义成了一个静态方法,并且需要传递一个参数cls。
Cls表示需要实例化的类,此参数在实例化时由Python解析器自动提供。
new()是在新式类中新出现的方法,它作用在构造方法init()建造实例之前,
可以这么理解,在Python 中存在于类里面的构造方法init()负责将类的实例化,
而在init()调用之前,new()决定是否要使用该init()方法,因为new()可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。
- 使用
类名()
创建对象的时候,python 解释器首先会调用__new__
方法为对象分配空间 __new__
是一个由 object 基类提供的内置的静态方法,主要作用有两个:1.在内存中为对象分配空间;2.返回对象的引用- python 解释器获得对象的引用后,将引用作为第一个参数,传递给
__init__
方法
方法:重写__new__
方法的代码非常固定
- 重写
__new__
方法一定要return super().__new__(cls)
- 否则 python 解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法
- 注意:
__new__
是一个静态方法,在调用时需要主动传递cls参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|