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