协议和接口
python中的协议和接口的概念不一样,接口是类似其他编程语言中一样比较严格的定义方式,而协议的概念有种约定俗成的味道。python中的有种鸭子类型的称呼,即说的就是通过协议的方式实现的行为。例如,python中的序列,如果通过抽象类的继承的方式就需要实现Sequence类下的多个抽象方法,而鸭子类型就让我们只通过实现getitem方法就能让我们的类的部分表现像序列一样,可以通过下标进行取值。
协议是一种非正式的接口,因此不能像正式接口那样施加限制。一个类可以实现多个协议,从而令类的表现像从多个继承来的一样(事实上并没有)。
抽象类
python中也可以像其他语言一样定义抽象类,不过大多时候不需要这么做,常见的做法是继承已经存在的抽象基类。
下面说明如何继承一个已经存在的抽象类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31from collections import MutableSequence, namedtuple
Card = namedtuple("Card", ["rank", "suit"])
class FrenchDeck(MutableSequence):
ranks = [str(n) for n in range(2, 11)] + list("JKQA")
suits = 'spades diamonds clubs harts'.split()
def __init__(self):
self._cards = [Card(rank, suit)
for suit in self.suits for rank in self.ranks]
def __getitem__(self, position):
return self._cards[position]
def __setitem__(self, position, value):
self._cards[position] = value
def __len__(self):
return len(self._cards)
def __delitem__(self,position):
del self._cards[position]
def insert(self,position,value):
self._cards.insert(position,value)
f = FrenchDeck()
继承抽象基类的子类必须全部实现抽象基类中的抽象方法,像例子中的getitem,__setitem__,__len__,__delitem__,insert这些方法是必须实现的,否则在运行过程中python会告诉你没有实现某个方法而报警:1
2
3
4Traceback (most recent call last):
File "pythonscripts/abc_frenchdeck.py", line 29, in <module>
f = FrenchDeck()
TypeError: Can't instantiate abstract class FrenchDeck with abstract methods __delitem__, insert
collections.abc模块中的抽象基类
标准库中的抽象基类大多数在collections.abc模块中定义,部分在numbers和io中。
常见的抽象基类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Iterable
Container
Sized
Callable
Hashable
Iterator
Sequence
Mapping
Set
MappingView
MutableSequence
MutableMapping
MutableSet
ItemsView
KeysView
ValuesView
自定义抽象基类
尽管不推荐自定义抽象基类,但是在某些特殊的情况下还是有需要自己创建抽象基类的,创建抽象基类需要继承自abc.ABC
1 | import abc |
abc.ABC是从python3.4后新增的,对于之前的版本必须使用metaclass=关键字:1
class animal(metaclass=abc.ABCMeta):
对于python2 则只能通过metaclass来实现了:1
2class animal(object):
__metaclass__ = abc.ABCMeta
虚拟子类
除了继承,其实还在另外一种将类注册为抽象基类的子类的方法,通过register方法.
例如,我们实现一个dog类,但不继承:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19@animal.register
class dog:
def run(self):
print('10km/s')
def eat(self):
print('eat meat')
def shit(self):
print('pull shit')
d = dog()
print(isinstance(d,animal))
print(issubclass(dog,animal))
---------------
True
True
register方法可以通过装饰器的方式放在子类上(对于3.3版本以前的python只能调用标准的句法),由上面的例子可以看出来,isinstance和issubclass都认可子类的这个事实。
但是在mro中,却没有列出animal,说明dog并不是从mro中继承而来的:1
2
3print(dog.__mro__)
-----------------
(<class '__main__.dog'>, <class 'object'>)
另外一个事实
某些情况下,即使不使用register方法,也能把一个类注册为抽象基类的子类。看下面的例子:
1 | from collections import abc |
这里的子类只实现了一个len竟然也成了抽象基类Sized的子类,那么原理是什么呢?
这是因为Sized实现了一个特殊的方法subclasshook:1
2
3
4
5
6
7
8
9
10
11
12
13
14class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
if any("__len__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
由Sized源码我们可以看到了我们要的答案。
虽然这为我们实现提供了另外的一种思路,但是还是不推荐这么做,因为,Sized这里是个特例,len方法的目的十分明确,而在我们自己实现过程中,恐怕是很难有这么明确的行为目的的。