抽象类表示接口。 ---------Bjarne StroustrupC++之父
引入抽象基类之前,python就已经很成功了,即便现在也很少有代码使用抽象基类。协议可以看成是非正式的接口,是python这种动态语言实现动态的方式。
接口在动态语言中是如何运作的呢?首先,python中除了抽象基类,每个类都有接口:类实现和公开的属性,包括特殊方法如__getitem__, __iter__等。
关于接口,接口是实现特定角色的方法的集合。一个类可能会实现多个接口,从而让实例扮演多种角色。
协议是接口,但不是正式的,一个类允许只实现部分接口。有时,有些API只要求对象拥有.read()方法即可。
序列协议是pyhton最基础的协议之一。即便只实现了那个协议最基础的一部分,解释器也会对其进行处理。
python数据模型的哲学是尽量支持基本协议。对序列模型来说,即便是最简单的实现,python也力求做到最好。
序列Sequence的抽象基类接口:
我们自己实现一个Foo类,它并不继承abc.Sequence,而是实现序列协议的一个方法__getitem__。
1 class Foo: 2 def __getitem__(self, index): 3 return (range(30))[index] 4 5 foo = Foo() 6 for val in foo: 7 print(val) 8 print(foo[2]) 9 print(foo[-1]) 10 print(-7 in foo)
虽然我们并没有实现__iter__和__contains__方法,但是foo却是一个可迭代的对象,为什么呢?
我们定义了__getitem__方法,python会调用它,从下标为0开始,尝试着迭代对象(这属于python的一种后备机制)。同理,尽管并没有__contains__方法,但是仍然可以使用in运算符来遍历对象查找指定元素是否存在。
也就是说,如果没有__iter__和__contains__方法,python会退而求其次调用__getitem__方法,设法让迭代和in运算符可用。
1 class FrenchDeck: 2 ranks = [str(n) for n in range(2, 11)] + list(‘JQKA‘) 3 suits = ‘spades diamonds clubs hearts‘.split() 4 def __init__(self): 5 self._cards = [Card(rank, suit) for rank in self.ranks for suit in self.suits] 6 7 def __len__(self): 8 return len(self._cards) 9 10 def __getitem__(self, index): 11 return self._cards[index] 12 13 deck = FrenchDeck() 14 print(len(deck)) 15 print(deck[2])
FrenchDeck有个缺陷就是无法洗牌,random模块中有个shuffle方法。python文档对齐描述是就地打乱列表顺序。
1 import random 2 l = list(range(30)) 3 print(l) 4 random.shuffle(l) 5 print(l)
将shuffle应用在deck上:
解释器报错,根据异常信息可以知道,FrenchDeck不支持元素赋值,那就手动添加一下吧。
方法一:在类内实现__setitem__方法
1 class FrenchDeck: 2 ranks = [str(n) for n in range(2, 11)] + list(‘JQKA‘) 3 suits = ‘spades diamonds clubs hearts‘.split() 4 def __init__(self): 5 self._cards = [Card(rank, suit) for rank in self.ranks for suit in self.suits] 6 7 def __len__(self): 8 return len(self._cards) 9 10 def __getitem__(self, index): 11 return self._cards[index] 12 13 def __setitem__(self, index, val): 14 self._cards[index] = val 15 16 deck = FrenchDeck() 17 print(len(deck)) 18 print(deck[2]) 19 20 for card in deck: 21 print(card) 22 23 random.shuffle(deck) 24 for card in deck: 25 print(card)
方法二:打补丁(运行时添加)
1 class FrenchDeck: 2 ranks = [str(n) for n in range(2, 11)] + list(‘JQKA‘) 3 suits = ‘spades diamonds clubs hearts‘.split() 4 def __init__(self): 5 self._cards = [Card(rank, suit) for rank in self.ranks for suit in self.suits] 6 7 def __len__(self): 8 return len(self._cards) 9 10 def __getitem__(self, index): 11 return self._cards[index] 12 13 #def __setitem__(self, index, val): 14 # self._cards[index] = val 15 16 def set_card(deck, index, val): 17 deck._cards[index] = val 18 deck = FrenchDeck() 19 print(len(deck)) 20 print(deck[2]) 21 22 for card in deck: 23 print(card) 24 FrenchDeck.__setitem__ = set_card 25 random.shuffle(deck) 26 for card in deck: 27 print(card)
方法二在类外部定义了set_card函数,然后设置类属性__setitem__引用set_card,__setitem__接收三个参数,第一个是对象自身,第二个是索引下标,第三个是值,参数名是无关紧要的,在类内部方法第一个参数命名成self是中python约定俗成的惯例,你命名成其他名字除了不规范之外,对程序本身没有任何影响。
1 class Foo: 2 def f(python): 3 print(python) 4 print("--------") 5 6 foo = Foo() 7 foo.f()
方法二这种技术叫猴子补丁:在运行时修改类或模块,不改动源码。
此外,还说明一点,random.shuffle不关心它的参数类型,只要参数实现了序列协议即可,这就是鸭子类型。
鸭子类型:不关心对象的具体类型,对象只需实现特定的协议即可。
抽象基类是用于封装框架引入的一般性概念和抽象的,例如“一个序列”和“一个
确切的数”。(读者)基本上不需要自己编写新的抽象基类,只要正确使用现有的抽象基类,就能获得 99.9% 的好处,而不用冒着设计不当导致的巨大风险。
原文:https://www.cnblogs.com/forwardfeilds/p/10482179.html