Python进阶1-Python数据模型

Python数据模型

一致性:
1、python语言具有良好的一致性,比如获取字符创长度可以使用len()方法,获取列表(数组)长度也可以使用len()方法。在其他 语言中,对于不同的数据类型可能有不同的获取长度方法,在java中获取字符串长度使用 字符串名.length(),而获取列表长度使用list.size()。
2、Python能这样做,背后其实是一些特殊方法提供的支撑。这些特殊方法的名字以两个下划线开头,并以两个下划线结尾(如__len__, __getitem__),这些特殊的方法能够使自己定义类能够支持迭代、集合、属性访问、运算符重载、函数和方法调用、对象的销毁和创建、字符串表示形式和格式化、管理上下文。

特殊方法初体验

下面用一个个简单的例子来展示__len__, __getitem__的用法,有一副扑克牌我们希望能够获取有多少张牌,以及能够通过下标拿到一张牌。

from itertools import product
#声明Card类,表示扑克中的一张牌
class Card(object):

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return "Card(rank=%s, suit=%s)" % (self.rank, self.suit)

    def __str__(self):
        return "(%s, %s)" % (self.rank, self.suit)


#声明Poker类,表示一副扑克牌
class Poker(object):

    suits = ["spade", "heart", "diamond", "club"]
    ranks = [str(i) for i in range(2, 11)] + list("JQKA")

    def __init__(self):
        self._cards = [Card(*agrs) for agrs in product(self.ranks, self.suits)]

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

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

首先声明了一个简单的类Card来表示一张牌,这种只具有属性不具有方法的类,还可以用具名元组来构建

collections.namedtuple("Card", ["rank", "suit"])

实现了特殊方法__len__,那就可以用len()函数来查看一副扑克里有多少张牌:

>>> poker = Poker()
>>> len(poker)
52

实现了特殊方法__getitem__,那就通过下标从一副牌中抽取一张牌:

#这里Card实现了__repr__方法,所以打印的是Card的字符串表示形式
>>> poker[0]
Card(rank=2, suit=spade)
>>> poker[-1]
Card(rank=A, suit=club)
#将__repr__方法注释掉
>>> poker[-1]
<test.Card object at 0x000001E59568D438>

对于随机获取获取一张牌,我们不需要自己写一个方法来随机获取,python提供了一个从序列中随机获取元素的函数random.choice:

>>> from random import choice
>>> choice(poker)
Card(rank=Q, suit=heart)

从choice的源码中可以看出,return seq[i]使用了下标访问,最终还是调用了__getitem__这个特殊方法:

def choice(self, seq):
    """Choose a random element from a non-empty sequence."""
    try:
        i = self._randbelow(len(seq))
    except ValueError:
        raise IndexError('Cannot choose from an empty sequence') from None
    return seq[i]

在重写的__getitem__方法中,把[]操作交给了self._cards列表,所以Poker类也是支持切片操作的:

>>> poker = Poker()
>>> poker[:6]
[Card(rank=2, suit=spade), Card(rank=2, suit=heart), Card(rank=2, suit=diamond), Card(rank=2, suit=club), Card(rank=3, suit=spade), Card(rank=3, suit=heart)]

在Pycharm中打断点可以看到,切片操作时,__getitem__实际是拿到了slice对象,然后交给了self._cards列表处理
在这里插入图片描述
另外实现了__getitem__方法,poker就变为可迭代(反向迭代)的了:

>>> for card in poker:
...     print(card)
(2, spade)
(2, heart)
(2, diamond)
#反向迭代
>>> for card in reversed(poker):
...     print(card)
(A, club)
(A, diamond)
(A, heart)
(A, spade)

虽然这里没有实现__contains__方法,但是也可以使用in运算符。是因为当没有实现__contains__方式时,in运算就会迭代搜索,而Poker是可迭代的,所以也自动的支持in运算。

#使用具名元组表示一张牌,在Poker实现__getitem__方法后可直接使用in运算,因为元组有判断相等的方法
>>> Card("2","spade") in poker
True
>>> Card("2","spades") in poker
False
#使用Card类表示一张牌,在Poker实现__getitem__方法后不可直接使用in运算,因为没有判断相等的方法
>>> Card("2","spade") in poker
False
#需要在Card类,重写__eq__方法
def __eq__(self, other):
     return self.rank == other.rank and self.suit == other.suit
#再使用in运算
>>> Card("2","spade") in poker
True
>>> Card("2","spades") in poker
False

对于排序可以写一个排序方法,然后将这个方法传入sorted,即可实现升序排序:

def custom_sort(card):
    suit_values = {"spade": 3, "heart": 2, "diamond": 1, "club": 0}
    rank_value = Poker.ranks.index(card.rank)
    return rank_value * len(suit_values) + suit_values[card.suit]
>>> sorted(poker, key=custom_sort)
[Card(rank=2, suit=club), Card(rank=2, suit=diamond), Card(rank=2, suit=heart), Card(rank=2, suit=spade), Card(rank=3, suit=club), Card(rank=3, suit=diamond), Card(rank=3, suit=heart), Card(rank=3, suit=spade), ....]

通过上面这个例子可以感受到python数据模型有两个好处:
1、各种数据模型的操作是统一的,如果需要特殊定制化处理,重写相应的特殊方法即可
2、可以更加方便的使用python标准库,不用重复造轮子

特殊方法的使用

对于自己编写的类,想要支持python标准操作就需要实现特殊方法,然而并不需要调用他们(如:obj.len()),而是通过统一的函数去使用len(obj)

模拟向量

下面通过实现一些特殊方法来模拟一个二维向量

class Vector(object):

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

    #实现加法
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

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

    #数与向量的乘积
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

    def __repr__(self):
        return "Vector(%s, %s)" % (self.x, self.y)

    def __bool__(self):
        return abs(self)
>>> vector = Vector(1, 2)
Vector(1, 2)
>>> vector1 = Vector(1, 3)
Vector(1, 3)
>>> print(vector)
Vector(1, 2)
>>> print(vector * 2)
Vector(2, 4)
>>> print(vector + vector1)
Vector(2, 5)
>>> print(bool(vector))
True
>>> print(abs(vector))
2.23606797749979

__mul__和__add__为Vector提供了*和+这两个算术运算
__abs__为Vector提供了模长计算
__bool__为Vector提供了自定义的真假判断,可以改写为如下所示,省去了模长运算,执行效率更高

def __bool__(self):
    return bool(self.x or self.y)

__repr__把对象转化成字符串展示,且返回的字符串应该是准备的、无歧义的,能够尽可能的表达出如何用代码创建这个被打印的对象
__repr__和__str__区别: __str__在str()函数中被调用,或是在print函数打印时调用,如果一个对象没有__str__函数,而python又需要调用它的时候,解释器就会用__repr__代替

特殊方法列表

与运算符无关的特殊方法

类别方法名
字符串、字节序列表示形式__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__,__subclasschek__

与运算符相关的特殊方法

类别方法名
一元运算符__eng__ -,__pos__ +,__abs__ abs()
比较运算符__lt__ <,__le__ <=,__eq__ ==,__ne__ !=,__gt__ >,__ge__ >=
算术运算符__add__ +,__sub__ -,__mul__ *,__delitem__,__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__
反向位运算符__rlshift__,__rrshift__,__rand__,__ror__,__rxor__
增量赋值位运算符__irlshift__,__irrshift__,__irand__,__iror__,__irxor__

ps:当交换两个操作数位置时就会调用反向运算符(b * a而不是a * b);增量赋值运算符就是a *= b

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值