Skip to content

数据模型

数据模型其实是对 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
import collections
from random import choice

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck(object):
    ranks = [str(i) for i in range(2, 11)] + list("JQKA")
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

deck = FrenchDeck()
print(len(deck))
print(deck[0])
print(deck[-1])
print(choice(deck))
print(choice(deck))
print(deck[:3])
print(deck[12::13])
# for card in deck:
#     print(card)
# for card in reversed(deck):
#     print(card)
print(Card('Q', 'hearts') in deck)
print(Card('7', 'beasts') in deck)

"""
52
Card(rank='2', suit='spades')
Card(rank='A', suit='hearts')
Card(rank='Q', suit='clubs')
Card(rank='8', suit='hearts')
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
True
False
"""

按照常规,用点数来判定扑克牌的大小,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
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)

def spades_high(card):
    rank_value = FrenchDeck.ranks.index(card.rank)
    return rank_value * len(suit_values) + suit_values[card.suit]
for card in sorted(deck, key=spades_high):
    print(card)
"""
Card(rank='2', suit='clubs')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='hearts')
Card(rank='2', suit='spades')
Card(rank='3', suit='clubs')
Card(rank='3', suit='diamonds')
...
Card(rank='K', suit='spades')
Card(rank='A', suit='clubs')
Card(rank='A', suit='diamonds')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')
"""

一个简单的二维向量类

 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
from math import hypot

class Vector:

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __abs__(self):
        return hypot(self.x, self.y)

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

    def __bool__(self):
        print("used __bool__ func")
        # return bool(abs(self))
        return bool(self.x or self.y)

v1 = Vector(2, 4)
v2 = Vector(2, 1)
print(v1 + v2)
v = Vector(3, 4)
print(abs(v))
print(v * 3)
print(abs(v * 3))
if v1:
    print(True)
"""
Vector(4, 5)
5.0
Vector(9, 12)
15.0
used __bool__ func
True
"""

字符串表示形式

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
class MusicPlayer(object):

    def __new__(cls, *args, **kwargs):
        print("this is new 方法")

    def __init__(self):
        print("this is init 方法")


music = MusicPlayer()
print(music)

# this is new 方法
# None
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class MusicPlayer(object):

    def __new__(cls, *args, **kwargs):
        print("this is new 方法")

        #为对象分配空间:super().__new__(cls)
        #返回对象的引用
        return super().__new__(cls)

    def __init__(self):
        print("this is init 方法")


music = MusicPlayer()
print(music)

# this is new 方法
# this is init 方法
# <__main__.MusicPlayer object at 0x7f8143ad74e0>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __new__(cls, name, age):
        if 0 < age < 150:
            # return object.__new__(cls)
            # return super(Person, cls).__new__(cls)
            return super().__new__(cls)

    def __str__(self):
        return "{0}({1})".format(self.__class__.__name__, self.__dict__)


print(Person("Tom", 10))
print(Person("Mike", 200))

# Person({'name': 'Tom', 'age': 10})
# None