在java里面,继承都是单继承的,只有接口才支持多继承,而且接口是不支持实例化的,同样在Python里面的抽象基类也是不支持实例化的,这一点要非常明确。

其次,在动态语言(Python,JavaScript等)里面,是没有变量的类型的,它只是一个符号,可以指向任何类型的变量,所以它就不支持多态了,从语言上来说就是多态语言了。动态语言在声明变量的时候,是不用声明变量的类型的,但是也就少了编译时检查错误的环节,这样我们写的代码出了错如果不运行,我们是很难知道的。这也就是动态语言与静态语言相比的一个劣势,无法做错误检查(没有编译,无法检查)。

在Python设计的理念里,鸭子类型是很重要的,我们要把它放在第一位。在java里面,某个类具有某个功能是看它继承了某个函数,但是在Python里面则是具有了某些魔法函数,就具有了某个功能。(鸭子类型和魔法函数构成了我们Python里面的协议)

回过来,我们谈抽象基类,抽象基类的意思是在这个基类里面,我们设定一些方法,然后所有继承这个基类的类都必须覆盖这个基类的方法;抽象基类是无法进行实例化的。可能还是不是很明白,我们通过两种情况来加深对它的理解。

第一种情况:我们去检查某个类是否具有某个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Company(object):
def __init__(self, employee_list):
self.employee = employee_list

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


com = Company(["tom", "bob", "jane"])
print(len(com))

# 输出结果: 3

上面的例子我们可以知道这个Company类是具有长度的。那么现在又有问题了,我们如何知道这个类是否具有某个方法呢?Python里面内置了一个函数hasattr

1
2
3
print(hasattr(com, "__len__"))

# 输出结果: True

但是这样我们觉得不是很方便,如果可以判断某个对象的类型就好了,只要是某个类型就具有该类型的方法,这样就好用多了。Python里面内置了一个函数isinstance,我们可以用到抽象基类里面的Sized

1
2
3
4
5
# 我们希望在某些情况之下判定某个对象的类型
from collections.abc import Sized
print(isinstance(com, Sized))

# 输出结果: True

另一种情况就是需要强制某个子类必须实现某种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 我们需要强制某个子类必须实现某种方法
# 如何自己定义一个抽象基类,从而指定子类必须实现的方法呢?

class CacheBase(object):
def get(self, key):
pass

def set(self, key, value):
raise NotImplementedError


class RedisCache(CacheBase):
def set(self, key, value):
pass


redisss = RedisCache
redisss().set("key", "value")

# 正常运行

在上面的例子中,我们RedisCache这个类继承了CacheBase这个类,但是又重写了它的set方法,如果我们不去重写它的set方法,结果会是怎样?我们来运行一下:

1
2
3
4
5
6
7
8
9
10
11
12

class RedisCache(CacheBase):
pass


redisss = RedisCache
redisss().set("key", "value")

# 运行结果:
raise NotImplementedError
NotImplementedError

你肯定也猜到了这一点,所以我们需要强制我们的子类继承我们基类的同时必须重写基类的方法。

不过这个函数有一个缺点,就是只有在调用的时候才会出异常,我们现在试着改造一下它,使它在初始化的时候就能抛出异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import abc # 全局abc


class CacheBase(metaclass=abc.ABCMeta): # 元类编程里面会介绍

@abc.abstractmethod
def get(self, key):
pass

@abc.abstractmethod
def set(self, key, value):
raise NotImplementedError


class RedisCache(CacheBase):
pass


redisss = RedisCache()

# 我们在初始化的时候就出了问题;
redisss = RedisCache()
TypeError: Can't instantiate abstract class RedisCache with abstract methods get, set

我们尝试去重写一个set方法,然后再运行一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import abc # 全局abc


class CacheBase(metaclass=abc.ABCMeta): # 元类编程里面会介绍

@abc.abstractmethod
def get(self, key):
pass

@abc.abstractmethod
def set(self, key, value):
raise NotImplementedError


class RedisCache(CacheBase):
def set(self, key, value):
raise NotImplementedError


redisss = RedisCache()

# 运行结果:
redisss = RedisCache()
TypeError: Can't instantiate abstract class RedisCache with abstract methods get

这样我们重写了set方法就不会报错了。

番外:python的抽象基类和java的abstact class和interface的区别是什么?

java的继承模式是只能继承一个类,但是可以继承多个接口。这个是为了代码重用和设计便于理解。

python的抽象基类在很多程度上并不是为了解决这个问题,python的抽象基类最重要的是为了实现类别的判断以及强制实现某些方法,从概念上来讲听起来好像差不多,但实际上你对python的抽象基类了解以后,也会发现在python中判断某个类型的类别实际上还是去判断某个类是否有某个具体的方法,所以本质上python是基于协议的一种语言,也就是方法名称更重要, 抽象基类只不过是是的判断某个类具有哪些方法名用起来更方便而已。所以你可以发现你如果某个类中实现了某个方法, 如果这个方法正好和某个抽象类的方法名一致,你即使不继承这个抽象基类你也会判断到你当前的类是基类的实例,所以抽象基类并不是很重要,方法名是最重要的。但是java的类型则是严格的继承关系,最多是可以动态的将某个子类赋给父类而已。