第一章Python 数据模型

数据模型其实是对Python框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括但不限于序列、迭代器、函数、类和上下文管理器。

       magic and dunder:魔术方法(magic method)是特殊方法的昵称。有些开发者在提到__getitem__这个特殊方法的时候,会用诸如“下划线--下划线--getitem”这种说法。但会引起歧义,因为像__x这种命名在Python里面也有其他含义。一位大牛用了“双下 -- getitem”这种说法。于是特殊方法也称为双下方法(dunder method)

(一)一摞Python风格的纸牌

Propaedeutics:

Python元组的升级版本 -- namedtuple(具名元组)

因为元组的局限性:不能为元组内部的数据进行命名,所以往往我们并不知道一个元组所要表达的意义,所以在这里引入了 collections.namedtuple 这个工厂函数,来构造一个带字段名的元组。具名元组的实例和普通元组消耗的内存一样多,因为字段名都被存在对应的类里面。这个类跟普通的对象实例比起来也要小一些,因为 Python 不会用 __dict__ 来存放这些实例的属性。

namedtuple 对象的定义如以下格式:

collections.namedtuple(typename, field_names, verbose=False, rename=False) 
返回一个具名元组子类 typename,其中参数的意义如下:
  typename:元组名称
  field_names: 元组中元素的名称
  rename: 如果元素名称中含有 python 的关键字,则必须设置为 rename=True
  verbose: 默认就好

 

import collections
from random import choice

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

class FrenchDeck:
    # ranks = [str(n) for n in range(2,11)] + list('JQAK')
    rank = [str(n) for n in range(2,11)] + list('JQAK')
    suits = 'spades diamonds clubs hearts'.split()

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

    def __len__(self):
        return len(self._cards)
    """
    如果在类中定义了__getitem__()方法,那么它的实例对象(假设为P)就可以这样P[key]取值。
    当实例对象做P[key]运算时,就会调用类中的__getitem__()方法。
    """
    def __getitem__(self, position):
        return self._cards[position]



beer_card = Card('7','diamonds')
print(beer_card)   # Card(rank='7', suit='diamonds')

deck = FrenchDeck()
# 使用len()函数查看一叠牌多少张
print(len(deck))   # 52
# 抽取特定的一张牌 ,这都是由__getitem__方法提供的
print(deck[0])     # Card(rank='2', suit='spades')
print(deck[-1])    # Card(rank='K', suit='hearts')
# 随机抽取一张牌,使用内置的随机选出一个元素的函数 random.choice
print(choice(deck))# Card(rank='J', suit='hearts')
print(choice(deck))# Card(rank='5', suit='hearts')
# 由于__getitem__方法把[]操作交给了self._cards列表故支持切片操作(slicing)操作
print(deck[:3])    # [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
print(deck[12::13])

# 另外仅仅实现了 __getitem__函数,这摞牌就变成可迭代了
for card in deck:
    print(card)
# 方向迭代操作
for card in reversed(deck):
    print(card)

# 迭代通常是隐示的,若一个集合类型没有实现__contains__方法,那么in运算符就会按照顺序做一次迭代搜索
print(Card('Q','hearts')in deck)

# 排序操作
suit_values = dict(spades = 3,hearts = 2,diamonds = 1,clubs = 0)
def spades_high(card):
    rank_value = FrenchDeck.rank.index(card.rank)
    return rank_value * len(suit_values) + suit_values[card.suit]

for card in sorted(deck,key= spades_high):
    print(card)

(二)如何使用特殊方法?

  首先特殊方法的存在是为了被Python解释器调用的,你自己并不需要调用它。也就是说没有 obj.__len__() 这种写法,而是使用 len(obj) 这种写法。

    1、若obj是你自己自定义类对象,Python会去调用你写的__len__方法。

    2、若obj是Python内置的类型,比如列表(list)、字符串(str)、字节序列(bytearray)等,那么CPython会抄个近路,__len__实际上会返回PyVarObject里的ob_size属性。PyVarObject是表示内存中长度可变的内置对象的C语言结构体。直接读取这个值比调用方法快的多。

       很多时候,特殊方法的调用是隐式的,比如for I in x:这个语句,背后其实用的是iter(x),而这个函数背后则是x.__iter__()方法。当然前提是这个方法在x中被实现了。

(三)模拟数值类型

案例:实现二维向量类

Propaedeutics:

  abs:是一个内置的函数,输入整数或浮点数,它返回输入值的绝对值;如果输入是复数(complex number),则返回复数的模。

from math import hypot

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

    def __repr__(self):
        return 'Vector(%r,%r)' % (self.x,self.y)

    # hypot 返回欧几里德范数 sqrt(x*x + y*y)
    def __abs__(self):
        return hypot(self.x,self.y)

    def __bool__(self):
        return bool(abs(self))

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

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

v1 = Vector(2,4)
v2 = Vector(2,1)
print(v1 + v2)

  

猜你喜欢

转载自www.cnblogs.com/IamJiangXiaoKun/p/10208000.html