记录博客 ZH-BLOG

Python 数据模型

时间:2018-07-27 23:06:28分类:python

python 特殊方法

1. __len__ 和 __getitem__

import collections,random

# 用以构建只有少数属性但是没有方法的对象
Card=collections.namedtuple('Card',['rank','suit']) # 牌 [点数,花色]
print(isinstance(Card,tuple))   # False
print(Card)

class FrenchDeck():
    ranks=[str(i) for i in range(2,11)]+list('JQKA')
    suits='spades diamons clubs hearts'.split() # 默认空格分隔

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

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

    def __getitem__(self, item):
        return self._card[item]


deck=FrenchDeck()
print(len(deck))
print(deck[1])
card=deck[1]
print(card.suit,card.rank)
# 随机返回一张牌
print(random.choice(deck))
print(deck[:3]) # 取出前三张牌
print(deck[12::13]) # 取出所有的'A' ,索引[12,12+13,12+13+13,12+13+13+13]

for card in deck:   # 实现了  __getitem__ ,可以迭代
    print(card)

for card in reversed(deck): # 反向迭代
    print(card)

print(Card('8','diamons') in deck)  # 实现了 __getitem__ 与 __len__ 可用 in 判断了

# 用点数来判定扑克牌的大小, 2 最小、 A
# 最大; 同时还要加上对花色的判定, 黑桃最大、 红桃次之、 方块再次、
# 梅花最小。 下面就是按照这个规则来给扑克牌排序的函数, 梅花 2 的大
# 小是 0, 黑桃 A 是 51
suit_values=dict(spades=3,hearts=2,diamons=1,clubs=0)
def spades_high(card):
    rank_value=deck.ranks.index(card.rank)
    return rank_value*len(suit_values)+suit_values[card.suit]

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

实现了 __len__ 方法,可以用 len() 函数查看纸牌的数量。

实现了 __getitem__ 方法,可以抽取特定的一张纸牌;可以更加方便地利用 Python 的标准库, 比如 random.choice 函数;自动支持切片(slicing) 操作;另外, 仅仅实现了 __getitem__ 方法, 这一摞牌就变成可迭代的了。

通过实现 __len__ 和 __getitem__ 这两个特殊方法, FrenchDeck 就跟一个 Python 自有的序列数据类型一样, 可以体现出 Python 的核心语言特性(例如迭代和切片) 。 同时这个类还可以用于标准库中诸如random.choice、 reversed 和 sorted 这些函数。

注:特殊方法的调用是隐式的, 比如 for i in x: 这个语句,背后其实用的是 iter(x),自己并不需要调用它们。 也就是说没有 my_object.__len__() 这种写法。


2. __repr__、 __abs__、 __add__ 和 __mul__

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)

    def __abs__(self):
        return hypot(self.x,self.y) # (x*x + y*y)开方

    # 默认情况下, 我们自己定义的类的实例总被认为是真的, 除非这个类对
    # __bool__ 或者 __len__ 函数有自己的实现。 bool(x) 的背后是调用 x.__bool__() 的结果;
    # 如果不存在 __bool__ 方法, 那么 bool(x) 会尝试调用 x.__len__()。
    # 若返回 0, 则 bool 会返回 False; 否则返回 True。
    def __bool__(self):
        # return bool(abs(self))
        return bool(self.x or self.y)   # 更高效

    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)

v=Vector(3,4)
print(abs(v))
print(v*3)
print(abs(v*3))

hypot(self.x,self.y) 相当于 sqrt(x*x + y*y)

__repr__ 和 __str__ 的区别在于,__repr__ 是 repr() 和控制台交互输出结果,后者是在 str() 函数被使用, 或是在用 print 函数打印一个对象的时候才被调用的, 并且它返回的字符串对终端用户更友好。如果你只想实现这两个特殊方法中的一个,__repr__ 是更好的选择,因为如果一个对象没有 __str__ 函数, 而 Python 又需要调用它的时候, 解释器会用 __repr__ 作为替代。如:

# class Student:
# def __repr__(self):
# return 'abc'
#
# def __str__(self):
# return '123'
#
# >> > Student
# <class '__main__.Student'>
# >> > Student()
# abc
# >> > print(Student)
# <class '__main__.Student'>
# >> > print(Student())
# 123

__abs__ 方法会被 python 内置函数 abs(obj) 调用

__add__ 方法当对象进行 + 号操作时调用

__mul__ 方法当对象进行 * 号操作时调用


Python 特殊方法总览:

https://docs.python.org/3/reference/datamodel.html